summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEthan Werner <mail@ethanwerner.com>2026-07-01 09:42:39 +0400
committerEthan Werner <mail@ethanwerner.com>2026-07-01 09:42:39 +0400
commit07e18043145f3d91c8ea7ec3ad41b3c905585277 (patch)
tree2bdeae46973f4bdfce263679a4008ac768011f28
initial commit: ts time series analysis library v0.1.0
- 354 financial and mathematical time-series indicators - Uniform C11 API with consistent function signatures - 349 passing tests with -Werror - Static and shared library builds - pkg-config integration - Python bindings - Fuzzing and sanitizer support - Security hardening flags
-rwxr-xr-x.githooks/pre-commit28
-rw-r--r--.gitignore50
-rw-r--r--CHANGELOG.md17
-rw-r--r--LICENSE21
-rw-r--r--Makefile224
-rw-r--r--README.md132
-rw-r--r--VERSION1
-rw-r--r--lib/absolute_value.c40
-rw-r--r--lib/acceleration_bands.c81
-rw-r--r--lib/acosh.c41
-rw-r--r--lib/ad.c48
-rw-r--r--lib/ad_oscillator.c67
-rw-r--r--lib/addition.c41
-rw-r--r--lib/adx.c84
-rw-r--r--lib/alligator.c70
-rw-r--r--lib/angle_deg_to_rad.c40
-rw-r--r--lib/angle_normalize.c46
-rw-r--r--lib/angle_rad_to_deg.c40
-rw-r--r--lib/annualized_historical_volatility.c61
-rw-r--r--lib/apo.c68
-rw-r--r--lib/aroon.c67
-rw-r--r--lib/aroon_oscillator.c71
-rw-r--r--lib/asi.c53
-rw-r--r--lib/asinh.c42
-rw-r--r--lib/atan2.c42
-rw-r--r--lib/atanh.c50
-rw-r--r--lib/atr.c73
-rw-r--r--lib/atr_bands.c87
-rw-r--r--lib/atr_trailing_stops.c88
-rw-r--r--lib/average.c71
-rw-r--r--lib/average_cumulative.c47
-rw-r--r--lib/average_exponential.c56
-rw-r--r--lib/average_exponential_double.c57
-rw-r--r--lib/average_exponential_triple.c57
-rw-r--r--lib/average_price.c39
-rw-r--r--lib/average_triangle.c107
-rw-r--r--lib/average_weighted.c67
-rw-r--r--lib/average_weighted_series.c78
-rw-r--r--lib/awesome_oscillator.c66
-rw-r--r--lib/balance_of_power.c41
-rw-r--r--lib/band_width.c60
-rw-r--r--lib/beta_pdf.c52
-rw-r--r--lib/binomial.c58
-rw-r--r--lib/binomial_pdf.c63
-rw-r--r--lib/bollinger_bands.c88
-rw-r--r--lib/bollinger_lower.c60
-rw-r--r--lib/bollinger_pctb.c63
-rw-r--r--lib/bollinger_upper.c60
-rw-r--r--lib/bollinger_width.c62
-rw-r--r--lib/candlestick_formations.c58
-rw-r--r--lib/catalan.c54
-rw-r--r--lib/cauchy_cdf.c40
-rw-r--r--lib/cauchy_pdf.c39
-rw-r--r--lib/cbrt.c41
-rw-r--r--lib/cci.c89
-rw-r--r--lib/ceiling.c42
-rw-r--r--lib/center_of_gravity.c56
-rw-r--r--lib/chande_forecast_oscillator.c67
-rw-r--r--lib/change.c47
-rw-r--r--lib/channel.c64
-rw-r--r--lib/chebyshev_distance.c39
-rw-r--r--lib/chebyshev_t.c61
-rw-r--r--lib/chi_squared_pdf.c49
-rw-r--r--lib/choppiness_index.c68
-rw-r--r--lib/close_minus_open.c39
-rw-r--r--lib/close_to_high.c40
-rw-r--r--lib/close_to_open_ratio.c39
-rw-r--r--lib/cmf.c67
-rw-r--r--lib/cmo.c72
-rw-r--r--lib/coppock_curve.c75
-rw-r--r--lib/copysign.c39
-rw-r--r--lib/correlation_coefficient.c75
-rw-r--r--lib/cosecant.c43
-rw-r--r--lib/cosecant_hyperbolic.c45
-rw-r--r--lib/cosecant_hyperbolic_inverse.c44
-rw-r--r--lib/cosecant_inverse.c44
-rw-r--r--lib/cosine.c42
-rw-r--r--lib/cosine_hyperbolic.c43
-rw-r--r--lib/cosine_inverse.c43
-rw-r--r--lib/cosine_similarity.c40
-rw-r--r--lib/cotangent.c43
-rw-r--r--lib/cotangent_hyperbolic.c46
-rw-r--r--lib/cotangent_hyperbolic_inverse.c45
-rw-r--r--lib/cotangent_inverse.c46
-rw-r--r--lib/crossover.c60
-rw-r--r--lib/cumulative_product.c42
-rw-r--r--lib/cumulative_sum.c42
-rw-r--r--lib/cumulative_volume_delta.c44
-rw-r--r--lib/cv.c56
-rw-r--r--lib/darvas_box.c54
-rw-r--r--lib/dawsn.c64
-rw-r--r--lib/decay_exponential.c58
-rw-r--r--lib/decay_linear.c85
-rw-r--r--lib/degree_radian.c39
-rw-r--r--lib/delta.c50
-rw-r--r--lib/deviation_average.c78
-rw-r--r--lib/deviation_standard.c70
-rw-r--r--lib/deviation_standard_average_exponential.c57
-rw-r--r--lib/deviation_vector.c61
-rw-r--r--lib/di_minus.c69
-rw-r--r--lib/di_plus.c69
-rw-r--r--lib/digamma.c72
-rw-r--r--lib/disparity_index.c58
-rw-r--r--lib/distance.c39
-rw-r--r--lib/division.c41
-rw-r--r--lib/donchian_channel.c60
-rw-r--r--lib/donchian_width.c56
-rw-r--r--lib/dpo.c78
-rw-r--r--lib/dx.c79
-rw-r--r--lib/ease_of_movement.c71
-rw-r--r--lib/elder_force_index.c52
-rw-r--r--lib/elder_impulse.c70
-rw-r--r--lib/elder_ray.c58
-rw-r--r--lib/ellint_1.c65
-rw-r--r--lib/ellint_2.c64
-rw-r--r--lib/ema_variable.c48
-rw-r--r--lib/envelope.c60
-rw-r--r--lib/erf.c38
-rw-r--r--lib/erfc.c38
-rw-r--r--lib/euclidean_norm.c38
-rw-r--r--lib/exp10.c40
-rw-r--r--lib/exp10m1.c38
-rw-r--r--lib/exp2.c40
-rw-r--r--lib/exp2m1.c38
-rw-r--r--lib/expit.c40
-rw-r--r--lib/expm1.c40
-rw-r--r--lib/exponential.c49
-rw-r--r--lib/exponential_cdf.c38
-rw-r--r--lib/exponential_natural.c41
-rw-r--r--lib/exponential_pdf.c38
-rw-r--r--lib/factorial.c47
-rw-r--r--lib/factorial_int.c45
-rw-r--r--lib/fdim_2input.c38
-rw-r--r--lib/fibonacci.c49
-rw-r--r--lib/fill_indicator.c63
-rw-r--r--lib/fisher_transform.c78
-rw-r--r--lib/floor.c41
-rw-r--r--lib/fma.c38
-rw-r--r--lib/fmax.c38
-rw-r--r--lib/fmax_2input.c38
-rw-r--r--lib/fmin.c38
-rw-r--r--lib/fmin_2input.c38
-rw-r--r--lib/fmod.c38
-rw-r--r--lib/fobv.c43
-rw-r--r--lib/fractal_chaos_bands.c74
-rw-r--r--lib/fractal_chaos_oscillator.c73
-rw-r--r--lib/gamma_pdf.c48
-rw-r--r--lib/gator_oscillator.c64
-rw-r--r--lib/gaussian_cdf.c43
-rw-r--r--lib/gaussian_pdf.c42
-rw-r--r--lib/gaussian_quantile.c94
-rw-r--r--lib/gcd.c50
-rw-r--r--lib/gopalakrishnan_range_index.c64
-rw-r--r--lib/hard_sigmoid.c47
-rw-r--r--lib/hard_swish.c47
-rw-r--r--lib/hard_tanh.c48
-rw-r--r--lib/harmonic.c50
-rw-r--r--lib/hermite.c64
-rw-r--r--lib/high_low_bands.c71
-rw-r--r--lib/high_minus_low.c42
-rw-r--r--lib/highest_high.c67
-rw-r--r--lib/historical_volatility.c62
-rw-r--r--lib/hull_ma.c89
-rw-r--r--lib/hurst_exponent.c122
-rw-r--r--lib/hypot.c42
-rw-r--r--lib/ichimoku.c113
-rw-r--r--lib/ilogb.c42
-rw-r--r--lib/intraday_momentum_index.c57
-rw-r--r--lib/is_prime.c69
-rw-r--r--lib/jaccard_similarity.c45
-rw-r--r--lib/kalman_filter.c63
-rw-r--r--lib/kaufman_adaptive_ma.c61
-rw-r--r--lib/keltner_channel.c78
-rw-r--r--lib/kvo.c105
-rw-r--r--lib/lag.c54
-rw-r--r--lib/laguerre.c62
-rw-r--r--lib/lambert_w.c64
-rw-r--r--lib/laplace_cdf.c43
-rw-r--r--lib/laplace_pdf.c42
-rw-r--r--lib/lcm.c62
-rw-r--r--lib/leaky_relu.c42
-rw-r--r--lib/legendre.c64
-rw-r--r--lib/lerp.c42
-rw-r--r--lib/lgamma.c52
-rw-r--r--lib/linear_regression.c86
-rw-r--r--lib/linear_regression_angle.c80
-rw-r--r--lib/linear_regression_forecast.c82
-rw-r--r--lib/linear_regression_intercept.c79
-rw-r--r--lib/linear_regression_r2.c88
-rw-r--r--lib/linear_regression_slope.c77
-rw-r--r--lib/linear_weighted_ma.c56
-rw-r--r--lib/log1p.c41
-rw-r--r--lib/log1pm1.c41
-rw-r--r--lib/log_return.c50
-rwxr-xr-xlib/logarithm.c50
-rw-r--r--lib/logarithm_binary.c44
-rw-r--r--lib/logarithm_decimal.c44
-rw-r--r--lib/logarithm_natural.c45
-rw-r--r--lib/logb.c42
-rw-r--r--lib/lowest_low.c66
-rw-r--r--lib/macd.c87
-rw-r--r--lib/manhattan_distance.c40
-rw-r--r--lib/manhattan_norm.c40
-rw-r--r--lib/market_facilitation_index.c39
-rw-r--r--lib/mass_index.c91
-rwxr-xr-xlib/maximum.c56
-rw-r--r--lib/median_price.c39
-rw-r--r--lib/mesa_sine_wave.c112
-rw-r--r--lib/mfi.c84
-rw-r--r--lib/midpoint.c53
-rw-r--r--lib/midprice.c53
-rwxr-xr-xlib/minimum.c56
-rw-r--r--lib/minmax.c53
-rw-r--r--lib/missing_bar_count.c51
-rw-r--r--lib/momentum.c46
-rw-r--r--lib/money_flow_multiplier.c42
-rw-r--r--lib/moving_linear_regression.c83
-rw-r--r--lib/moving_linear_regression_r2.c88
-rw-r--r--lib/multiplication.c39
-rw-r--r--lib/nan_fn.c41
-rw-r--r--lib/natr.c57
-rw-r--r--lib/nearbyint.c40
-rw-r--r--lib/nextafter.c40
-rw-r--r--lib/nexttoward.c40
-rw-r--r--lib/none.c36
-rw-r--r--lib/nvi.c51
-rw-r--r--lib/obv.c54
-rw-r--r--lib/on_balance_volume_rate.c74
-rw-r--r--lib/open_interest.c40
-rw-r--r--lib/open_minus_close.c39
-rw-r--r--lib/oscillator_cross.c70
-rw-r--r--lib/osma.c78
-rw-r--r--lib/parabolic_sar.c99
-rw-r--r--lib/performance_index.c39
-rw-r--r--lib/performance_ratio.c50
-rw-r--r--lib/pivot_points.c51
-rw-r--r--lib/plot.c39
-rw-r--r--lib/poisson_pdf.c48
-rw-r--r--lib/polynomial.c41
-rwxr-xr-xlib/power.c71
-rw-r--r--lib/ppo.c94
-rw-r--r--lib/pretty_good_oscillator.c64
-rw-r--r--lib/price_channel.c58
-rw-r--r--lib/price_momentum_oscillator.c45
-rw-r--r--lib/price_rate_of_change.c45
-rw-r--r--lib/prime_number_bands.c58
-rw-r--r--lib/prime_number_oscillator.c82
-rw-r--r--lib/prings_kst.c79
-rw-r--r--lib/prings_special_k.c86
-rw-r--r--lib/psychological_line.c56
-rw-r--r--lib/pvi.c61
-rw-r--r--lib/pvt.c46
-rw-r--r--lib/qstick.c50
-rw-r--r--lib/radian_degree.c39
-rw-r--r--lib/rainbow_ma.c61
-rw-r--r--lib/rainbow_oscillator.c68
-rw-r--r--lib/random_walk_index.c64
-rw-r--r--lib/rank.c54
-rwxr-xr-xlib/ratio.c49
-rw-r--r--lib/ravi.c88
-rw-r--r--lib/reciprocal.c39
-rwxr-xr-xlib/regression_linear_least_squares.c92
-rw-r--r--lib/relative_strength.c41
-rw-r--r--lib/relative_volatility.c75
-rw-r--r--lib/relu.c40
-rw-r--r--lib/remainder.c41
-rw-r--r--lib/remquo_dummy.c40
-rw-r--r--lib/reversal.c52
-rw-r--r--lib/rint.c40
-rw-r--r--lib/roc.c47
-rw-r--r--lib/rocp.c46
-rw-r--r--lib/rocr.c46
-rw-r--r--lib/rocr100.c45
-rwxr-xr-xlib/root.c48
-rw-r--r--lib/root_cube.c41
-rw-r--r--lib/root_square.c43
-rw-r--r--lib/round_.c40
-rw-r--r--lib/rsi.c91
-rw-r--r--lib/running_count.c43
-rw-r--r--lib/schaff_trend_cycle.c151
-rw-r--r--lib/secant.c41
-rw-r--r--lib/secant_hyperbolic.c42
-rw-r--r--lib/secant_hyperbolic_inverse.c42
-rw-r--r--lib/secant_inverse.c42
-rw-r--r--lib/session_volume.c43
-rw-r--r--lib/shinohara_intensity_ratio.c85
-rw-r--r--lib/sigmoid.c63
-rw-r--r--lib/sign.c41
-rw-r--r--lib/sinc.c47
-rw-r--r--lib/sinc_unnormalized.c44
-rw-r--r--lib/sine.c40
-rw-r--r--lib/sine_hyperbolic.c41
-rw-r--r--lib/sine_hyperbolic_inverse.c41
-rw-r--r--lib/sine_inverse.c41
-rwxr-xr-xlib/slope.c51
-rw-r--r--lib/smootherstep.c49
-rw-r--r--lib/smoothstep.c48
-rw-r--r--lib/softplus.c41
-rw-r--r--lib/softsign.c41
-rw-r--r--lib/standard_error.c67
-rw-r--r--lib/starc_bands.c70
-rw-r--r--lib/starc_lower.c74
-rw-r--r--lib/starc_upper.c64
-rw-r--r--lib/std_error.c57
-rw-r--r--lib/stoch_rsi.c96
-rw-r--r--lib/stochastic_fast.c94
-rw-r--r--lib/stochastic_momentum_index.c130
-rw-r--r--lib/stochastic_slow.c91
-rw-r--r--lib/student_t_pdf.c51
-rw-r--r--lib/subtraction.c39
-rwxr-xr-xlib/summation.c57
-rw-r--r--lib/supertrend.c118
-rw-r--r--lib/swing_index.c52
-rw-r--r--lib/swish.c41
-rw-r--r--lib/synthetic_yield.c47
-rw-r--r--lib/t3.c66
-rw-r--r--lib/tangent.c40
-rw-r--r--lib/tangent_hyperbolic.c41
-rw-r--r--lib/tangent_hyperbolic_inverse.c43
-rw-r--r--lib/tangent_inverse.c40
-rw-r--r--lib/tanhshrink.c41
-rwxr-xr-xlib/test.c77
-rw-r--r--lib/tgamma.c42
-rw-r--r--lib/tick_volume.c41
-rw-r--r--lib/time_series_forecast.c58
-rw-r--r--lib/trade_volume_index.c43
-rw-r--r--lib/trend_intensity_index.c62
-rwxr-xr-xlib/trix.c61
-rw-r--r--lib/true_range.c45
-rw-r--r--lib/truncate.c40
-rwxr-xr-xlib/ts.c119
-rw-r--r--lib/twiggs_money_flow.c67
-rw-r--r--lib/typical_price.c41
-rw-r--r--lib/ulcer_index.c76
-rw-r--r--lib/ultimate_oscillator.c88
-rw-r--r--lib/valuation_lines.c62
-rw-r--r--lib/variable_index_dynamic_average.c65
-rwxr-xr-xlib/variance.c72
-rwxr-xr-xlib/variance_exponential.c51
-rw-r--r--lib/vertical_horizontal_filter.c75
-rw-r--r--lib/volatility_stop.c74
-rw-r--r--lib/volatility_system.c55
-rw-r--r--lib/volume_delta.c60
-rw-r--r--lib/volume_moving_average.c48
-rw-r--r--lib/volume_oscillator.c57
-rw-r--r--lib/volume_rate_of_change.c46
-rw-r--r--lib/volume_underlay.c45
-rw-r--r--lib/volume_weighted_moving_average.c58
-rw-r--r--lib/vortex_indicator.c64
-rw-r--r--lib/vwap.c44
-rw-r--r--lib/vwap_period.c58
-rw-r--r--lib/weibull_pdf.c46
-rw-r--r--lib/weighted_close.c39
-rw-r--r--lib/wilders_smoothing.c52
-rw-r--r--lib/williams_r.c86
-rw-r--r--lib/zero_lag_ema.c53
-rw-r--r--lib/zeta.c52
-rw-r--r--lib/zigzag.c99
-rwxr-xr-xmain.c92
-rw-r--r--python/pyproject.toml36
-rw-r--r--python/ts/py.typed0
-rw-r--r--scripts/gen_all.py135
-rw-r--r--scripts/gen_bindings.py274
-rw-r--r--scripts/gen_flat.py96
-rw-r--r--scripts/gen_fuzz.py74
-rw-r--r--scripts/gen_registry.py103
-rw-r--r--scripts/ts_parser.py159
-rw-r--r--test/absolute_value.c13
-rw-r--r--test/acceleration_bands.c28
-rw-r--r--test/acosh.c27
-rw-r--r--test/ad.c18
-rw-r--r--test/ad_oscillator.c46
-rw-r--r--test/addition.c14
-rw-r--r--test/adx.c61
-rw-r--r--test/alligator.c21
-rw-r--r--test/angle_deg_to_rad.c13
-rw-r--r--test/angle_normalize.c11
-rw-r--r--test/angle_rad_to_deg.c11
-rw-r--r--test/annualized_historical_volatility.c15
-rw-r--r--test/apo.c41
-rw-r--r--test/aroon.c21
-rw-r--r--test/aroon_oscillator.c22
-rw-r--r--test/asi.c35
-rw-r--r--test/asinh.c13
-rw-r--r--test/atan2.c14
-rw-r--r--test/atanh.c35
-rw-r--r--test/atr.c19
-rw-r--r--test/atr_bands.c29
-rw-r--r--test/atr_trailing_stops.c26
-rw-r--r--test/average.c15
-rw-r--r--test/average_cumulative.c13
-rw-r--r--test/average_exponential.c16
-rw-r--r--test/average_exponential_double.c15
-rw-r--r--test/average_exponential_triple.c15
-rw-r--r--test/average_price.c17
-rw-r--r--test/average_triangle.c15
-rw-r--r--test/average_weighted.c15
-rw-r--r--test/average_weighted_series.c16
-rw-r--r--test/awesome_oscillator.c58
-rw-r--r--test/balance_of_power.c17
-rw-r--r--test/band_width.c19
-rw-r--r--test/bench.c204
-rw-r--r--test/beta_pdf.c15
-rw-r--r--test/binomial.c14
-rw-r--r--test/binomial_pdf.c14
-rw-r--r--test/bollinger_bands.c22
-rw-r--r--test/bollinger_lower.c16
-rw-r--r--test/bollinger_pctb.c19
-rw-r--r--test/bollinger_upper.c16
-rw-r--r--test/bollinger_width.c17
-rw-r--r--test/candlestick_formations.c32
-rw-r--r--test/catalan.c13
-rw-r--r--test/cauchy_cdf.c13
-rw-r--r--test/cauchy_pdf.c13
-rw-r--r--test/cbrt.c13
-rw-r--r--test/cci.c26
-rw-r--r--test/ceiling.c14
-rw-r--r--test/center_of_gravity.c17
-rw-r--r--test/chande_forecast_oscillator.c15
-rw-r--r--test/change.c15
-rw-r--r--test/channel.c23
-rw-r--r--test/chebyshev_distance.c14
-rw-r--r--test/chebyshev_t.c14
-rw-r--r--test/chi_squared_pdf.c15
-rw-r--r--test/choppiness_index.c23
-rw-r--r--test/close_minus_open.c17
-rw-r--r--test/close_to_high.c17
-rw-r--r--test/close_to_open_ratio.c17
-rw-r--r--test/cmf.c41
-rw-r--r--test/cmo.c25
-rw-r--r--test/coppock_curve.c26
-rw-r--r--test/copysign.c14
-rw-r--r--test/correlation_coefficient.c16
-rw-r--r--test/cosecant.c13
-rw-r--r--test/cosecant_hyperbolic.c9
-rw-r--r--test/cosecant_hyperbolic_inverse.c9
-rw-r--r--test/cosecant_inverse.c9
-rw-r--r--test/cosine.c13
-rw-r--r--test/cosine_hyperbolic.c13
-rw-r--r--test/cosine_inverse.c13
-rw-r--r--test/cosine_similarity.c14
-rw-r--r--test/cotangent.c9
-rw-r--r--test/cotangent_hyperbolic.c9
-rw-r--r--test/cotangent_hyperbolic_inverse.c9
-rw-r--r--test/cotangent_inverse.c9
-rw-r--r--test/crossover.c14
-rw-r--r--test/cumulative_product.c13
-rw-r--r--test/cumulative_sum.c13
-rw-r--r--test/cumulative_volume_delta.c27
-rw-r--r--test/cv.c25
-rw-r--r--test/darvas_box.c23
-rw-r--r--test/dawsn.c13
-rw-r--r--test/decay_exponential.c15
-rw-r--r--test/decay_linear.c14
-rw-r--r--test/degree_radian.c14
-rw-r--r--test/delta.c16
-rw-r--r--test/deviation_average.c15
-rw-r--r--test/deviation_standard.c15
-rw-r--r--test/deviation_standard_average_exponential.c15
-rw-r--r--test/deviation_vector.c15
-rw-r--r--test/di_minus.c20
-rw-r--r--test/di_plus.c20
-rw-r--r--test/digamma.c11
-rw-r--r--test/disparity_index.c27
-rw-r--r--test/distance.c14
-rw-r--r--test/division.c15
-rw-r--r--test/donchian_channel.c26
-rw-r--r--test/donchian_width.c21
-rw-r--r--test/dpo.c24
-rw-r--r--test/dx.c22
-rw-r--r--test/ease_of_movement.c43
-rw-r--r--test/edge_cases.c324
-rw-r--r--test/elder_force_index.c32
-rw-r--r--test/elder_impulse.c22
-rw-r--r--test/elder_ray.c24
-rw-r--r--test/ellint_1.c13
-rw-r--r--test/ellint_2.c11
-rw-r--r--test/ema_variable.c15
-rw-r--r--test/envelope.c19
-rw-r--r--test/erf.c13
-rw-r--r--test/erfc.c13
-rw-r--r--test/euclidean_norm.c14
-rw-r--r--test/exp10.c13
-rw-r--r--test/exp10m1.c13
-rw-r--r--test/exp2.c13
-rw-r--r--test/exp2m1.c13
-rw-r--r--test/expit.c13
-rw-r--r--test/expm1.c13
-rw-r--r--test/exponential.c15
-rw-r--r--test/exponential_cdf.c13
-rw-r--r--test/exponential_natural.c13
-rw-r--r--test/exponential_pdf.c13
-rw-r--r--test/factorial.c13
-rw-r--r--test/factorial_int.c13
-rw-r--r--test/fdim_2input.c13
-rw-r--r--test/fibonacci.c13
-rw-r--r--test/fill_indicator.c40
-rw-r--r--test/fisher_transform.c26
-rw-r--r--test/floor.c13
-rw-r--r--test/fma.c15
-rw-r--r--test/fmax.c14
-rw-r--r--test/fmax_2input.c13
-rw-r--r--test/fmin.c14
-rw-r--r--test/fmin_2input.c13
-rw-r--r--test/fmod.c14
-rw-r--r--test/fobv.c18
-rw-r--r--test/fractal_chaos_bands.c21
-rw-r--r--test/fractal_chaos_oscillator.c20
-rw-r--r--test/fuzz.c68
-rw-r--r--test/gamma_pdf.c15
-rw-r--r--test/gator_oscillator.c24
-rw-r--r--test/gaussian_cdf.c13
-rw-r--r--test/gaussian_pdf.c13
-rw-r--r--test/gaussian_quantile.c11
-rw-r--r--test/gcd.c14
-rw-r--r--test/gen_test_data.c101
-rw-r--r--test/gopalakrishnan_range_index.c39
-rw-r--r--test/hard_sigmoid.c13
-rw-r--r--test/hard_swish.c13
-rw-r--r--test/hard_tanh.c13
-rw-r--r--test/harmonic.c13
-rw-r--r--test/hermite.c14
-rw-r--r--test/high_low_bands.c25
-rw-r--r--test/high_minus_low.c17
-rw-r--r--test/highest_high.c19
-rw-r--r--test/historical_volatility.c16
-rw-r--r--test/hull_ma.c15
-rw-r--r--test/hurst_exponent.c11
-rw-r--r--test/hypot.c14
-rw-r--r--test/ichimoku.c49
-rw-r--r--test/ilogb.c13
-rw-r--r--test/intraday_momentum_index.c39
-rw-r--r--test/is_prime.c13
-rw-r--r--test/jaccard_similarity.c14
-rw-r--r--test/kalman_filter.c26
-rw-r--r--test/kaufman_adaptive_ma.c15
-rw-r--r--test/keltner_channel.c23
-rw-r--r--test/kvo.c40
-rw-r--r--test/lag.c15
-rw-r--r--test/laguerre.c14
-rw-r--r--test/lambert_w.c11
-rw-r--r--test/laplace_cdf.c13
-rw-r--r--test/laplace_pdf.c13
-rw-r--r--test/lcm.c14
-rw-r--r--test/leaky_relu.c13
-rw-r--r--test/legendre.c14
-rw-r--r--test/lerp.c15
-rw-r--r--test/lgamma.c13
-rw-r--r--test/linear_regression.c15
-rw-r--r--test/linear_regression_angle.c15
-rw-r--r--test/linear_regression_forecast.c15
-rw-r--r--test/linear_regression_intercept.c15
-rw-r--r--test/linear_regression_r2.c15
-rw-r--r--test/linear_regression_slope.c15
-rw-r--r--test/linear_weighted_ma.c23
-rw-r--r--test/log1p.c13
-rw-r--r--test/log1pm1.c13
-rw-r--r--test/log_return.c16
-rw-r--r--test/logarithm.c15
-rw-r--r--test/logarithm_binary.c13
-rw-r--r--test/logarithm_decimal.c13
-rw-r--r--test/logarithm_natural.c13
-rw-r--r--test/logb.c13
-rw-r--r--test/lowest_low.c19
-rw-r--r--test/macd.c25
-rw-r--r--test/main.c73
-rw-r--r--test/manhattan_distance.c14
-rw-r--r--test/manhattan_norm.c14
-rw-r--r--test/market_facilitation_index.c23
-rw-r--r--test/mass_index.c26
-rw-r--r--test/maximum.c15
-rw-r--r--test/median_price.c17
-rw-r--r--test/mesa_sine_wave.c22
-rw-r--r--test/mfi.c59
-rw-r--r--test/midpoint.c15
-rw-r--r--test/midprice.c15
-rw-r--r--test/minimum.c15
-rw-r--r--test/minmax.c17
-rw-r--r--test/missing_bar_count.c28
-rw-r--r--test/momentum.c19
-rw-r--r--test/money_flow_multiplier.c17
-rw-r--r--test/moving_linear_regression.c15
-rw-r--r--test/moving_linear_regression_r2.c15
-rw-r--r--test/multiplication.c14
-rw-r--r--test/nan_fn.c13
-rw-r--r--test/natr.c22
-rw-r--r--test/nearbyint.c13
-rw-r--r--test/nextafter.c14
-rw-r--r--test/nexttoward.c14
-rw-r--r--test/none.c13
-rw-r--r--test/nvi.c38
-rw-r--r--test/obv.c18
-rw-r--r--test/on_balance_volume_rate.c45
-rw-r--r--test/open_interest.c14
-rw-r--r--test/open_minus_close.c17
-rw-r--r--test/oscillator_cross.c40
-rw-r--r--test/osma.c20
-rw-r--r--test/parabolic_sar.c22
-rw-r--r--test/performance_index.c16
-rw-r--r--test/performance_ratio.c17
-rw-r--r--test/pivot_points.c27
-rw-r--r--test/plot.c14
-rw-r--r--test/poisson_pdf.c15
-rw-r--r--test/polynomial.c15
-rw-r--r--test/power.c15
-rw-r--r--test/ppo.c21
-rw-r--r--test/pretty_good_oscillator.c27
-rw-r--r--test/price_channel.c21
-rw-r--r--test/price_momentum_oscillator.c15
-rw-r--r--test/price_rate_of_change.c27
-rw-r--r--test/prime_number_bands.c17
-rw-r--r--test/prime_number_oscillator.c23
-rw-r--r--test/prings_kst.c55
-rw-r--r--test/prings_special_k.c23
-rw-r--r--test/psychological_line.c23
-rw-r--r--test/pvi.c34
-rw-r--r--test/pvt.c27
-rw-r--r--test/qstick.c24
-rw-r--r--test/radian_degree.c14
-rw-r--r--test/rainbow_ma.c13
-rw-r--r--test/rainbow_oscillator.c14
-rw-r--r--test/random_walk_index.c19
-rw-r--r--test/rank.c20
-rw-r--r--test/ratio.c15
-rw-r--r--test/ravi.c66
-rw-r--r--test/reciprocal.c10
-rw-r--r--test/regression_linear_least_squares.c19
-rw-r--r--test/relative_strength.c14
-rw-r--r--test/relative_volatility.c21
-rw-r--r--test/relu.c13
-rw-r--r--test/remainder.c14
-rw-r--r--test/remquo_dummy.c11
-rw-r--r--test/reversal.c19
-rw-r--r--test/rint.c13
-rw-r--r--test/roc.c19
-rw-r--r--test/rocp.c19
-rw-r--r--test/rocr.c19
-rw-r--r--test/rocr100.c19
-rw-r--r--test/root.c15
-rw-r--r--test/root_cube.c13
-rw-r--r--test/root_square.c13
-rw-r--r--test/round_.c13
-rw-r--r--test/rsi.c30
-rw-r--r--test/running_count.c13
-rw-r--r--test/schaff_trend_cycle.c48
-rw-r--r--test/secant.c9
-rw-r--r--test/secant_hyperbolic.c9
-rw-r--r--test/secant_hyperbolic_inverse.c9
-rw-r--r--test/secant_inverse.c9
-rw-r--r--test/session_volume.c15
-rw-r--r--test/shinohara_intensity_ratio.c34
-rw-r--r--test/sigmoid.c15
-rw-r--r--test/sign.c9
-rw-r--r--test/sinc.c13
-rw-r--r--test/sinc_unnormalized.c13
-rw-r--r--test/sine.c13
-rw-r--r--test/sine_hyperbolic.c13
-rw-r--r--test/sine_hyperbolic_inverse.c13
-rw-r--r--test/sine_inverse.c13
-rw-r--r--test/slope.c15
-rw-r--r--test/smootherstep.c13
-rw-r--r--test/smoothstep.c13
-rw-r--r--test/softplus.c13
-rw-r--r--test/softsign.c13
-rw-r--r--test/standard_error.c15
-rw-r--r--test/starc_bands.c25
-rw-r--r--test/starc_lower.c22
-rw-r--r--test/starc_upper.c22
-rw-r--r--test/std_error.c17
-rw-r--r--test/stoch_rsi.c22
-rw-r--r--test/stochastic_fast.c23
-rw-r--r--test/stochastic_momentum_index.c25
-rw-r--r--test/stochastic_slow.c24
-rw-r--r--test/student_t_pdf.c15
-rw-r--r--test/subtraction.c14
-rw-r--r--test/summation.c15
-rw-r--r--test/supertrend.c31
-rw-r--r--test/swing_index.c35
-rw-r--r--test/swish.c13
-rw-r--r--test/synthetic_yield.c24
-rw-r--r--test/t3.c29
-rw-r--r--test/tangent.c14
-rw-r--r--test/tangent_hyperbolic.c13
-rw-r--r--test/tangent_hyperbolic_inverse.c13
-rw-r--r--test/tangent_inverse.c13
-rw-r--r--test/tanhshrink.c13
-rw-r--r--test/test_python.py782
-rw-r--r--test/tgamma.c13
-rw-r--r--test/tick_volume.c14
-rw-r--r--test/time_series_forecast.c15
-rw-r--r--test/trade_volume_index.c29
-rw-r--r--test/trend_intensity_index.c41
-rw-r--r--test/trix.c15
-rw-r--r--test/true_range.c18
-rw-r--r--test/truncate.c13
-rw-r--r--test/twiggs_money_flow.c41
-rw-r--r--test/typical_price.c17
-rw-r--r--test/ulcer_index.c28
-rw-r--r--test/ultimate_oscillator.c90
-rw-r--r--test/valuation_lines.c23
-rw-r--r--test/variable_index_dynamic_average.c15
-rw-r--r--test/variance.c15
-rw-r--r--test/variance_exponential.c15
-rw-r--r--test/vertical_horizontal_filter.c26
-rw-r--r--test/volatility_stop.c27
-rw-r--r--test/volatility_system.c21
-rw-r--r--test/volume_delta.c39
-rw-r--r--test/volume_moving_average.c33
-rw-r--r--test/volume_oscillator.c34
-rw-r--r--test/volume_rate_of_change.c29
-rw-r--r--test/volume_underlay.c16
-rw-r--r--test/volume_weighted_moving_average.c39
-rw-r--r--test/vortex_indicator.c27
-rw-r--r--test/vwap.c28
-rw-r--r--test/vwap_period.c42
-rw-r--r--test/weibull_pdf.c15
-rw-r--r--test/weighted_close.c17
-rw-r--r--test/wilders_smoothing.c15
-rw-r--r--test/williams_r.c22
-rw-r--r--test/zero_lag_ema.c21
-rw-r--r--test/zeta.c13
-rw-r--r--test/zigzag.c29
-rw-r--r--ts.c290
-rw-r--r--ts.h361
-rw-r--r--ts.pc.in11
725 files changed, 30360 insertions, 0 deletions
diff --git a/.githooks/pre-commit b/.githooks/pre-commit
new file mode 100755
index 0000000..b931987
--- /dev/null
+++ b/.githooks/pre-commit
@@ -0,0 +1,28 @@
+#!/bin/sh
+set -e
+
+# Pre-commit hook: run tests before allowing commit
+# Skip with: SKIP_PRE_COMMIT=1 git commit
+# Skip on force push: git commit --no-verify
+
+if [ "$SKIP_PRE_COMMIT" = "1" ]; then
+ exit 0
+fi
+
+# Only run tests if C/H/Makefile changed
+staged=$(git diff --cached --name-only --diff-filter=d)
+if echo "$staged" | grep -qE '\.(c|h)$|^Makefile$'; then
+ # Run build check
+ if ! make >/dev/null 2>&1; then
+ echo "PRE-COMMIT FAILED: build did not succeed."
+ exit 1
+ fi
+
+ # Run tests
+ if ! make test >/dev/null 2>&1; then
+ echo "PRE-COMMIT FAILED: tests did not pass."
+ exit 1
+ fi
+fi
+
+exit 0
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8b615ce
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,50 @@
+# C build artifacts
+build/
+*.o
+*.so
+*.a
+*.d
+
+# Core dumps
+core
+
+# Python
+__pycache__/
+*.pyc
+*.egg-info/
+dist/
+.pytest_cache/
+.ruff_cache/
+
+# IDE / editors
+.vscode/
+.idea/
+*.swp
+*~
+.DS_Store
+
+# macOS
+*.dSYM/
+*.app
+
+# Coverage
+*.gcno
+*.gcda
+*.gcov
+
+# Distribution
+*.tar.gz
+*.tar.bz2
+*.zip
+
+# Patches
+*.orig
+*.rej
+
+# Generated files
+ts_generated.h
+test/main_generated.c
+lib/ts_flat_generated.c
+lib/ts_registry_generated.c
+test/fuzz_table_generated.c
+python/ts/__init__.py
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..db68a56
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,17 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+## [0.1.0] - 2026-06-29
+
+### Added
+- 354 financial and mathematical time-series indicators
+- Uniform C API with consistent function signatures
+- Comprehensive test suite (349 tests)
+- Static and shared library builds
+- pkg-config integration
+- Python bindings scaffold
+- Fuzzing support
+- Security hardening flags (-D_FORTIFY_SOURCE=2, -fstack-protector-strong)
+- Sanitizer support (ASAN, UBSan)
+- Valgrind memory checking
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..a2d9813
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025-2026 Ethan Werner
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..572e23b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,224 @@
+CC ?= gcc
+PREFIX ?= /usr/local
+VERSION := $(or $(shell tr -d '[:space:]' < VERSION 2>/dev/null), 0.0.0)
+SOVERSION := $(word 1,$(subst ., ,$(VERSION)))
+PYTHON3 := $(shell command -v python3 2>/dev/null)
+ifeq ($(PYTHON3),)
+ $(error python3 is required but not found)
+endif
+
+# Base flags
+CFLAGS_BASE = -std=c11 -Wall -Wextra -Wshadow -Wdouble-promotion -Wnull-dereference -Wimplicit-fallthrough -Wmisleading-indentation -pedantic -O2 -I. -Ilib -MMD -MP
+
+# Platform-specific
+UNAME_S := $(shell uname -s)
+ifeq ($(UNAME_S),Linux)
+ CFLAGS_BASE += -D_DEFAULT_SOURCE
+else ifeq ($(UNAME_S),Darwin)
+ CFLAGS_BASE += -D_DARWIN_C_SOURCE
+else
+ # BSD and others
+ CFLAGS_BASE += -D_BSD_SOURCE
+endif
+
+# Security hardening (optimized builds only)
+CFLAGS_HARDENING = -D_FORTIFY_SOURCE=2 -fstack-protector-strong -Wformat-security -fno-common
+
+CFLAGS := $(CFLAGS_BASE) $(CFLAGS_HARDENING)
+LDLIBS = -lm
+
+SRCDIR = lib
+BUILDDIR = build
+TESTDIR = test
+SCRIPTDIR = scripts
+TEST_BUILDDIR = $(BUILDDIR)/test
+
+SRCS = $(filter-out $(SRCDIR)/ts.c $(SRCDIR)/ts_flat_generated.c $(SRCDIR)/ts_registry_generated.c $(SRCDIR)/test.c $(wildcard $(SRCDIR)/ts_*.c),$(wildcard $(SRCDIR)/*.c))
+OBJS = $(patsubst $(SRCDIR)/%.c,$(BUILDDIR)/%.o,$(SRCS))
+DEPS = $(OBJS:.o=.d) $(BUILDDIR)/ts.d
+TS_OBJ = $(BUILDDIR)/ts.o
+
+SHARED_OBJS = $(patsubst $(SRCDIR)/%.c,$(BUILDDIR)/%.so.o,$(SRCS))
+SHARED_TS_OBJ = $(BUILDDIR)/ts.so.o
+FLAT_OBJ = $(BUILDDIR)/ts_flat_generated.so.o
+REG_OBJ = $(BUILDDIR)/ts_registry_generated.so.o
+
+TEST_SRCS = $(TESTDIR)/main.c $(filter-out $(TESTDIR)/main.c $(TESTDIR)/main_generated.c $(TESTDIR)/gen_test_data.c $(TESTDIR)/bench.c $(TESTDIR)/fuzz_table_generated.c,$(wildcard $(TESTDIR)/*.c))
+TEST_OBJS = $(patsubst $(TESTDIR)/%.c,$(BUILDDIR)/test/%.o,$(TEST_SRCS))
+
+MAIN = $(BUILDDIR)/ts
+TEST_BIN = $(BUILDDIR)/run_tests
+BENCH_BIN = $(BUILDDIR)/bench
+LIB = $(BUILDDIR)/libts.a
+SHLIB = $(BUILDDIR)/libts.so
+
+.PHONY: all test test-one test-python check valgrind clean sanitize sanitize-test lib shared install uninstall distclean dist bench gen-bindings
+
+all: $(MAIN)
+
+lib: $(LIB)
+
+shared: $(SHLIB) gen-bindings
+
+$(LIB): $(OBJS) $(TS_OBJ) | $(BUILDDIR)
+ ar rcs $@ $^
+
+ifeq ($(UNAME_S),Linux)
+ SHLIB_SONAME = -Wl,-soname,libts.so.$(SOVERSION)
+else ifeq ($(UNAME_S),Darwin)
+ SHLIB_SONAME = -Wl,-install_name,@rpath/libts.so.$(SOVERSION)
+else
+ SHLIB_SONAME = -Wl,-soname,libts.so.$(SOVERSION)
+endif
+
+$(SHLIB): $(SHARED_OBJS) $(SHARED_TS_OBJ) $(FLAT_OBJ) $(REG_OBJ) | $(BUILDDIR)
+ $(CC) -shared $(SHLIB_SONAME) -Wl,-rpath,'$$ORIGIN' -o $@ $^ $(LDLIBS)
+
+$(BUILDDIR):
+ @mkdir -p $@
+
+$(BUILDDIR)/%.o: $(SRCDIR)/%.c ts.h ts_generated.h | $(BUILDDIR)
+ $(CC) $(CFLAGS) -c $< -o $@
+
+$(BUILDDIR)/%.so.o: $(SRCDIR)/%.c ts.h ts_generated.h | $(BUILDDIR)
+ $(CC) $(CFLAGS) -fPIC -c $< -o $@
+
+$(BUILDDIR)/ts.o: ts.c | $(BUILDDIR)
+ $(CC) $(CFLAGS) -c ts.c -o $@
+
+$(BUILDDIR)/ts.so.o: ts.c | $(BUILDDIR)
+ $(CC) $(CFLAGS) -fPIC -c ts.c -o $@
+
+ts_generated.h: $(SCRIPTDIR)/gen_all.py $(filter-out $(SRCDIR)/ts_flat_generated.c $(SRCDIR)/ts_registry_generated.c,$(wildcard $(SRCDIR)/*.c))
+ python3 $< || (echo "ERROR: gen_all.py failed" && exit 1)
+
+test/main_generated.c: ts_generated.h
+ @touch $@
+
+$(SRCDIR)/ts_flat_generated.c: $(SCRIPTDIR)/gen_flat.py $(filter-out $(SRCDIR)/ts_flat_generated.c $(SRCDIR)/ts_registry_generated.c,$(wildcard $(SRCDIR)/*.c))
+ python3 $< || (echo "ERROR: gen_flat.py failed" && exit 1)
+
+$(FLAT_OBJ): $(SRCDIR)/ts_flat_generated.c ts.h | $(BUILDDIR)
+ $(CC) $(CFLAGS) -fPIC -c $< -o $@
+
+$(SRCDIR)/ts_registry_generated.c: $(SCRIPTDIR)/gen_registry.py $(filter-out $(SRCDIR)/ts_registry_generated.c $(SRCDIR)/ts_flat_generated.c,$(wildcard $(SRCDIR)/*.c))
+ python3 $< || (echo "ERROR: gen_registry.py failed" && exit 1)
+
+$(REG_OBJ): $(SRCDIR)/ts_registry_generated.c ts.h | $(BUILDDIR)
+ $(CC) $(CFLAGS) -fPIC -c $< -o $@
+
+$(TESTDIR)/fuzz_table_generated.c: $(SCRIPTDIR)/gen_fuzz.py $(filter-out $(SRCDIR)/ts_flat_generated.c $(SRCDIR)/ts_registry_generated.c,$(wildcard $(SRCDIR)/*.c))
+ python3 $< || (echo "ERROR: gen_fuzz.py failed" && exit 1)
+
+$(BUILDDIR)/test/fuzz.o: $(TESTDIR)/fuzz.c $(TESTDIR)/fuzz_table_generated.c ts.h | $(TEST_BUILDDIR)
+ $(CC) $(CFLAGS) -DTS_IMPLEMENTATION -c $< -o $@
+
+$(TEST_BUILDDIR):
+ @mkdir -p $@
+
+$(BUILDDIR)/test/%.o: $(TESTDIR)/%.c ts.h | $(TEST_BUILDDIR)
+ $(CC) $(CFLAGS) -DTS_IMPLEMENTATION -c $< -o $@
+
+$(MAIN): $(OBJS) $(TS_OBJ) main.c | $(BUILDDIR)
+ $(CC) $(CFLAGS) main.c $(OBJS) $(TS_OBJ) -o $@ $(LDLIBS)
+
+$(TEST_BIN): $(OBJS) $(TEST_OBJS) $(TS_OBJ) | $(BUILDDIR)
+ $(CC) $(CFLAGS) $(TEST_OBJS) $(OBJS) $(TS_OBJ) -o $@ $(LDLIBS)
+
+test: $(TEST_BIN)
+ ./$(TEST_BIN)
+
+$(BENCH_BIN): $(OBJS) $(TS_OBJ) test/bench.c | $(BUILDDIR)
+ $(CC) $(CFLAGS) test/bench.c $(OBJS) $(TS_OBJ) -o $@ $(LDLIBS)
+
+bench: $(BENCH_BIN)
+ ./$(BENCH_BIN)
+
+python/ts/__init__.py: $(SCRIPTDIR)/gen_bindings.py $(SCRIPTDIR)/ts_parser.py $(filter-out $(SRCDIR)/ts_flat_generated.c $(SRCDIR)/ts_registry_generated.c,$(wildcard $(SRCDIR)/*.c))
+ python3 $(SCRIPTDIR)/gen_bindings.py || (echo "ERROR: gen_bindings.py failed" && exit 1)
+
+gen-bindings: python/ts/__init__.py
+
+test-python: shared gen-bindings
+ LD_LIBRARY_PATH=$(BUILDDIR):$$LD_LIBRARY_PATH python3 $(TESTDIR)/test_python.py
+
+check:
+ $(MAKE) clean
+ $(MAKE) test CFLAGS="$(CFLAGS_BASE) $(CFLAGS_HARDENING) -Werror"
+
+test-one: $(OBJS) $(TS_OBJ)
+ifndef TEST
+ $(error TEST is not set. Usage: make test-one TEST=<name>)
+endif
+ @echo "$(TEST)" | grep -qE '^[a-z0-9_]+$$' || { echo "ERROR: TEST must match [a-z0-9_]+"; exit 1; }
+ @mkdir -p $(BUILDDIR)
+ @printf '#include <stdbool.h>\nextern bool ts_test_$(TEST)(void);\nint main(void){return ts_test_$(TEST)() ? 0 : 1;}\n' > $(BUILDDIR)/stub.c
+ $(CC) $(CFLAGS) -DTS_IMPLEMENTATION $(BUILDDIR)/stub.c $(TESTDIR)/$(TEST).c $(OBJS) $(TS_OBJ) -o $(BUILDDIR)/test_$(TEST) $(LDLIBS)
+ ./$(BUILDDIR)/test_$(TEST)
+
+valgrind: $(TEST_BIN)
+ valgrind --leak-check=full --error-exitcode=1 ./$(TEST_BIN)
+
+sanitize:
+ $(MAKE) clean
+ $(MAKE) CFLAGS="$(CFLAGS_BASE) -O0 -g -fsanitize=address,undefined -fno-common"
+
+sanitize-test:
+ @$(CC) -print-file-name=libasan.so || { echo "ERROR: AddressSanitizer (libasan) not found. Install libasan."; exit 1; }
+ $(MAKE) clean
+ $(MAKE) test CFLAGS="$(CFLAGS_BASE) -O0 -g -fsanitize=address,undefined -fno-common"
+
+install: ts_generated.h lib shared
+ install -d $(PREFIX)/include/ts
+ install -m 644 ts.h $(PREFIX)/include/ts/
+ install -m 644 ts_generated.h $(PREFIX)/include/ts/
+ install -d $(PREFIX)/lib
+ install -m 644 $(LIB) $(PREFIX)/lib/
+ install -m 755 $(SHLIB) $(PREFIX)/lib/libts.so.$(VERSION)
+ ln -sf libts.so.$(VERSION) $(PREFIX)/lib/libts.so.$(SOVERSION)
+ ln -sf libts.so.$(SOVERSION) $(PREFIX)/lib/libts.so
+ @if [ "$(shell id -u)" = "0" ]; then ldconfig 2>/dev/null || true; else echo "WARNING: run 'sudo ldconfig' to update linker cache"; fi
+ install -d $(PREFIX)/lib/pkgconfig
+ @test -f ts.pc.in || { echo "ERROR: ts.pc.in not found" && exit 1; }
+ sed 's|@PREFIX@|$(PREFIX)|;s|@VERSION@|$(VERSION)|' ts.pc.in > $(PREFIX)/lib/pkgconfig/ts.pc
+
+uninstall:
+ rm -f $(PREFIX)/include/ts/ts.h
+ rm -f $(PREFIX)/include/ts/ts_generated.h
+ rm -rf $(PREFIX)/include/ts
+ rm -f $(PREFIX)/lib/libts.a
+ rm -f $(PREFIX)/lib/libts.so $(PREFIX)/lib/libts.so.$(SOVERSION) $(PREFIX)/lib/libts.so.$(VERSION)
+ rm -f $(PREFIX)/lib/pkgconfig/ts.pc
+ @if [ "$(shell id -u)" = "0" ]; then ldconfig 2>/dev/null || true; fi
+
+clean:
+ rm -rf $(BUILDDIR)
+
+distclean: clean
+ rm -f $(SRCDIR)/ts_flat_generated.c
+ rm -f $(TESTDIR)/fuzz_table_generated.c
+ rm -f ts_generated.h
+ rm -f python/ts/__init__.py
+
+DIST_NAME = ts-$(VERSION)
+DIST_FILES = Makefile VERSION ts.pc.in LICENSE README.md ts.h ts_generated.h ts.c main.c .gitignore
+DIST_LIB = $(filter-out $(SRCDIR)/ts_flat_generated.c $(SRCDIR)/ts_registry_generated.c $(SRCDIR)/test.c,$(wildcard $(SRCDIR)/*.c))
+DIST_SCRIPTS = $(wildcard $(SCRIPTDIR)/*.py)
+DIST_TEST = $(filter-out $(TESTDIR)/fuzz_table_generated.c,$(wildcard $(TESTDIR)/*.c)) $(wildcard $(TESTDIR)/*.py)
+
+dist:
+ $(MAKE) clean
+ $(MAKE) lib
+ @test -s ts_generated.h || { echo "ERROR: ts_generated.h not found or empty" && exit 1; }
+ rm -rf $(DIST_NAME)
+ mkdir -p $(DIST_NAME)/lib $(DIST_NAME)/test $(DIST_NAME)/scripts
+ cp $(DIST_FILES) $(DIST_NAME)/
+ cp $(DIST_LIB) $(DIST_NAME)/lib/
+ cp $(DIST_SCRIPTS) $(DIST_NAME)/scripts/
+ cp $(DIST_TEST) $(DIST_NAME)/test/
+ tar czf $(DIST_NAME).tar.gz $(DIST_NAME)
+ rm -rf $(DIST_NAME)
+ @echo "Created $(DIST_NAME).tar.gz"
+
+-include $(DEPS)
+-include $(TEST_OBJS:.o=.d)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..fe8a3e2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,132 @@
+# ts — Time Series Analysis Library
+
+C11 library of 354 financial and mathematical time-series indicators.
+
+The library provides 354 indicators covering moving averages, oscillators,
+volatility measures, statistical functions, and more. See the source files
+in `lib/` for the complete list.
+
+**Requirements:** Any C11-compatible compiler, libm, Python 3 (for code generation).
+
+**Platform:** Little-endian only (x86, x86_64, AArch64). Big-endian is not supported.
+
+**Note:** Generated files (`ts_generated.h`, `lib/ts_flat_generated.c`,
+`lib/ts_registry_generated.c`) are regenerated automatically by the Makefile.
+If you modify indicator sources, run `make` to regenerate.
+
+## Quick Start
+
+```bash
+make # Build example binary
+make test # Run all tests
+make lib # Build static library (libts.a)
+make shared # Build shared library (libts.so)
+make install # Install to /usr/local (PREFIX overrideable)
+make check # Clean build with -Werror + tests
+make sanitize-test # ASAN + UBSan
+make uninstall # Remove installed files
+```
+
+## Usage
+
+```c
+#define TS_IMPLEMENTATION
+#include "ts.h"
+
+// Types: fp_t == double, ts_uint_t == uint64_t
+
+// Simple moving average (period=10)
+uint_t period = 10;
+fp_t input[100];
+ts_fill(input, 100, 0, 100); // fill with 0..100
+fp_t output[100];
+fp_t * const y[1] = { output };
+fp_t const * const x[1] = { input };
+
+ts_constant_t c;
+memset(&c, 0, sizeof(c));
+*(uint_t *)c.data = period;
+
+int ret = ts_average(y, 1, x, 1, 100, &c);
+if (ret == -1) { /* error: invalid params or insufficient data */ }
+
+// Valid output starts at index ts_average_offset(&c) == period - 1
+uint_t offset = ts_average_offset(&c);
+for (uint_t i = offset; i < 100; i++) {
+ // output[i] contains valid SMA values
+}
+```
+
+## API
+
+Every indicator shares the same signature:
+
+```c
+int ts_<name>(
+ fp_t * const *y, // output arrays
+ uint_t y_n, // number of output arrays
+ fp_t const * const *x, // input arrays
+ uint_t x_n, // number of input arrays
+ uint_t n, // number of data points
+ ts_constant_t const *c // parameters (opaque buffer)
+);
+```
+
+**Return values:** `0` on success, `-1` on error (invalid parameters, insufficient data).
+
+**Offsets:** Each indicator has a `ts_<name>_offset(c)` function. Output values before the offset are undefined. Valid output starts at index `offset`.
+
+**Parameters:** Pass a struct matching the indicator's expected layout. For simple indicators, a raw `uint_t` period works:
+```c
+uint_t k = 14;
+ts_rsi(y, 1, x, 1, n, &k); // k is read as ts_rsi_params_t{k}
+```
+
+**OHLCV access:** Multi-input functions use macros:
+- `ts_o(x, i)` — open
+- `ts_h(x, i)` — high
+- `ts_l(x, i)` — low
+- `ts_c(x, i)` — close
+- `ts_v(x, i)` — volume
+
+## Thread Safety
+
+The library is **thread-safe for concurrent reads** but has the following constraints:
+
+- Each indicator function is stateless and does not use global mutable state.
+- Multiple threads can safely call different indicator functions simultaneously.
+- The same indicator function can be called from multiple threads as long as each thread uses its own `x`, `y`, and `c` buffers.
+- `ts_constant()` allocates heap memory — `ts_constant_free()` must be called by the owning thread.
+- The function registry (`ts_registry_get`, `ts_fn_name_get`) returns pointers to static read-only data and is thread-safe.
+
+## Building
+
+| Target | Description |
+|--------|-------------|
+| `make` | Build example binary |
+| `make lib` | Static library |
+| `make shared` | Shared library |
+| `make test` | Run test suite |
+| `make test-one TEST=rsi` | Run single test |
+| `make check` | Clean build with `-Werror` + tests |
+| `make valgrind` | Memory leak check |
+| `make sanitize-test` | ASAN + UBSan (requires libasan) |
+| `make clean` | Remove build artifacts |
+| `make distclean` | Remove build + generated files |
+
+## Install
+
+```bash
+make install # /usr/local
+make install PREFIX=/opt/ts # custom prefix
+```
+
+Installs header, static library, shared library, and pkg-config file. Use with:
+
+```bash
+pkg-config --cflags --libs ts
+```
+
+## License
+
+MIT — Copyright (c) 2025-2026 Ethan Werner
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..6e8bf73
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+0.1.0
diff --git a/lib/absolute_value.c b/lib/absolute_value.c
new file mode 100644
index 0000000..10714b0
--- /dev/null
+++ b/lib/absolute_value.c
@@ -0,0 +1,40 @@
+// Absolute Value
+//
+// absolute_value - abs
+//
+// y[t] = abs( x[t] )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_absolute_value_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_absolute_value_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_absolute_value(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_absolute_value_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = fabs( x[0][i] );
+ }
+ return 0;
+}
+
diff --git a/lib/acceleration_bands.c b/lib/acceleration_bands.c
new file mode 100644
index 0000000..776b990
--- /dev/null
+++ b/lib/acceleration_bands.c
@@ -0,0 +1,81 @@
+// Acceleration Bands
+//
+// acceleration_bands - acc_bands
+//
+// Upper: SMA of highest high over period k
+// Middle: SMA of typical price over period k
+// Lower: SMA of lowest low over period k
+//
+// Inputs: 4 (OHLCV)
+// Outputs: 3 (Upper, Middle, Lower)
+// Constants: 1 (period)
+
+#include <stdlib.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_acceleration_bands_params_t;
+
+ts_parameter_t const ts_acceleration_bands_p = { .n_inputs = 4, .n_outputs = 3, .n_constants = 1, .params_size = sizeof(ts_acceleration_bands_params_t) };
+
+uint_t ts_acceleration_bands_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_acceleration_bands_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_acceleration_bands(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_acceleration_bands_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_acceleration_bands_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ fp_t *hh_r = malloc( n * sizeof( fp_t ) );
+ fp_t *ll_r = malloc( n * sizeof( fp_t ) );
+ fp_t *tp = malloc( n * sizeof( fp_t ) );
+ if ( !hh_r || !ll_r || !tp ) {
+ free( hh_r ); free( ll_r ); free( tp );
+ return -1;
+ }
+
+ for ( uint_t i = 0; i < n; ++i )
+ {
+ tp[i] = ( ts_h( x, i ) + ts_l( x, i ) + ts_c( x, i ) ) / 3.0;
+ fp_t hh = ts_h( x, i );
+ fp_t ll = ts_l( x, i );
+ for ( uint_t j = 1; j < k && i >= j; ++j )
+ {
+ if ( ts_h( x, i - j ) > hh ) hh = ts_h( x, i - j );
+ if ( ts_l( x, i - j ) < ll ) ll = ts_l( x, i - j );
+ }
+ hh_r[i] = hh;
+ ll_r[i] = ll;
+ }
+
+ for ( uint_t j = 0; j < n && j < k - 1; ++j ) { y[0][j] = 0; y[1][j] = 0; y[2][j] = 0; }
+ for ( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t sum_hh = 0, sum_ll = 0, sum_tp = 0;
+ for ( uint_t j = 0; j < k; ++j )
+ {
+ sum_hh += hh_r[i - j];
+ sum_ll += ll_r[i - j];
+ sum_tp += tp[i - j];
+ }
+ y[0][i] = sum_hh / (fp_t)k;
+ y[1][i] = sum_tp / (fp_t)k;
+ y[2][i] = sum_ll / (fp_t)k;
+ }
+
+ free( hh_r ); free( ll_r ); free( tp );
+ return 0;
+}
+
+
+
diff --git a/lib/acosh.c b/lib/acosh.c
new file mode 100644
index 0000000..2335168
--- /dev/null
+++ b/lib/acosh.c
@@ -0,0 +1,41 @@
+// Hyperbolic Arccosine
+//
+// acosh - inverse hyperbolic cosine
+//
+// y[t] = acosh( x[t] ) if x[t] >= 1
+// y[t] = NAN if x[t] < 1
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_acosh_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_acosh_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_acosh(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_acosh_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t v = x[0][i];
+ y[0][i] = v >= 1.0 ? acosh( v ) : (fp_t)NAN;
+ }
+ return 0;
+}
+
diff --git a/lib/ad.c b/lib/ad.c
new file mode 100644
index 0000000..fd0e40e
--- /dev/null
+++ b/lib/ad.c
@@ -0,0 +1,48 @@
+// Accumulation/Distribution Line
+//
+// ad - A/D
+//
+// Cumulative money flow: MF = ((C-L) - (H-C)) / (H-L) * V, AD = SUM(MF)
+//
+// Inputs: 5 (OHLCV)
+// Outputs: 1 (AD Line)
+// Constants: 0
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_ad_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_ad_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_ad(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_ad_p );
+ (void)c;
+ if( n == 0 ) return -1;
+
+ fp_t hl0 = ts_h( x, 0 ) - ts_l( x, 0 );
+ y[0][0] = hl0 != 0
+ ? ( ( ts_c( x, 0 ) - ts_l( x, 0 ) ) - ( ts_h( x, 0 ) - ts_c( x, 0 ) ) ) / hl0 * ts_v( x, 0 )
+ : 0;
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ fp_t hl = ts_h( x, i ) - ts_l( x, i );
+ fp_t mf = hl != 0
+ ? ( ( ts_c( x, i ) - ts_l( x, i ) ) - ( ts_h( x, i ) - ts_c( x, i ) ) ) / hl * ts_v( x, i )
+ : 0;
+ y[0][i] = y[0][i - 1] + mf;
+ }
+
+ return 0;
+}
+
diff --git a/lib/ad_oscillator.c b/lib/ad_oscillator.c
new file mode 100644
index 0000000..5021140
--- /dev/null
+++ b/lib/ad_oscillator.c
@@ -0,0 +1,67 @@
+// Accumulation/Distribution Oscillator
+//
+// ad_oscillator - ADX
+//
+// Difference between fast and slow EMA of the A/D line
+//
+// Inputs: 5 (OHLCV)
+// Outputs: 1 (Oscillator)
+// Constants: 2 (fast period, slow period)
+
+#include "ts.h"
+
+typedef struct {
+ fp_t fast;
+ fp_t slow;
+} ts_ad_oscillator_params_t;
+
+ts_parameter_t const ts_ad_oscillator_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 2, .params_size = sizeof(ts_ad_oscillator_params_t) };
+
+uint_t ts_ad_oscillator_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_ad_oscillator(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_ad_oscillator_p );
+ if ( !c ) return -1;
+ if( n == 0 ) return -1;
+
+ fp_t fast = ((ts_ad_oscillator_params_t *)c)->fast;
+ fp_t slow = ((ts_ad_oscillator_params_t *)c)->slow;
+ if (fast == 0 || slow == 0) return -1;
+ fp_t a_fast = 1.0 / fast;
+ fp_t a_slow = 1.0 / slow;
+
+ fp_t hl = ts_h( x, 0 ) - ts_l( x, 0 );
+ fp_t mf = hl != 0
+ ? ( ( ts_c( x, 0 ) - ts_l( x, 0 ) ) - ( ts_h( x, 0 ) - ts_c( x, 0 ) ) ) / hl * ts_v( x, 0 )
+ : 0;
+ fp_t ad = mf;
+ fp_t ema_f = ad;
+ fp_t ema_s = ad;
+ y[0][0] = 0;
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ hl = ts_h( x, i ) - ts_l( x, i );
+ mf = hl != 0
+ ? ( ( ts_c( x, i ) - ts_l( x, i ) ) - ( ts_h( x, i ) - ts_c( x, i ) ) ) / hl * ts_v( x, i )
+ : 0;
+ ad += mf;
+ ema_f = ema_f + a_fast * ( ad - ema_f );
+ ema_s = ema_s + a_slow * ( ad - ema_s );
+ y[0][i] = ema_f - ema_s;
+ }
+
+ return 0;
+}
+
+
+
diff --git a/lib/addition.c b/lib/addition.c
new file mode 100644
index 0000000..f5d0deb
--- /dev/null
+++ b/lib/addition.c
@@ -0,0 +1,41 @@
+// Addition
+//
+// addition - add
+//
+// y[t] = x[0][t] + x[1][t]
+//
+// X_n - 2
+// Y_n - 1
+// C_n - 0
+
+
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_addition_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_addition_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_addition(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_addition_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = x[0][i] + x[1][i];
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/adx.c b/lib/adx.c
new file mode 100644
index 0000000..505732b
--- /dev/null
+++ b/lib/adx.c
@@ -0,0 +1,84 @@
+// Adx
+//
+// adx
+//
+// Time series function
+//
+// See implementation
+//
+// Inputs: 4
+// Outputs: 4
+// Constants: 1
+
+
+#include <math.h>
+#include <stdint.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_adx_params_t;
+
+ts_parameter_t const ts_adx_p = { .n_inputs = 4, .n_outputs = 4, .n_constants = 1, .params_size = sizeof(ts_adx_params_t) };
+
+uint_t ts_adx_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_adx_params_t *)c)->k + 1 : 0 );
+}
+
+int ts_adx(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_adx_p );
+ if ( !c ) return -1;
+ TS_CHECK_INPUT(x, x_n, n);
+ uint_t k = ((ts_adx_params_t *)c)->k;
+ if (k == 0) return -1;
+
+ fp_t ema_plus = 0, ema_minus = 0, ema_tr = 0;
+ fp_t prev_dx = 0;
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ fp_t hdmd = ts_h( x, i ) - ts_h( x, i - 1 );
+ fp_t ldml = ts_l( x, i - 1 ) - ts_l( x, i );
+
+ fp_t plus_dm = hdmd > ldml && hdmd > 0 ? hdmd : 0;
+ fp_t minus_dm = ldml > hdmd && ldml > 0 ? ldml : 0;
+
+ fp_t tr = fmax( ts_h( x, i ) - ts_l( x, i ),
+ fmax( fabs( ts_h( x, i ) - ts_c( x, i - 1 ) ),
+ fabs( ts_l( x, i ) - ts_c( x, i - 1 ) ) ) );
+
+ ema_plus = i == 1 ? plus_dm
+ : ema_plus + ( 1.0 / k ) * ( plus_dm - ema_plus );
+ ema_minus = i == 1 ? minus_dm
+ : ema_minus + ( 1.0 / k ) * ( minus_dm - ema_minus );
+ ema_tr = i == 1 ? tr
+ : ema_tr + ( 1.0 / k ) * ( tr - ema_tr );
+
+ if( i > k )
+ {
+ fp_t di_plus = ema_tr != 0 ? 100.0 * ema_plus / ema_tr : 0;
+ fp_t di_minus = ema_tr != 0 ? 100.0 * ema_minus / ema_tr : 0;
+
+ y[0][i] = di_plus;
+ y[1][i] = di_minus;
+
+ fp_t sum = di_plus + di_minus;
+ fp_t dx = sum != 0 ? 100.0 * fabs( di_plus - di_minus ) / sum : 0;
+
+ y[2][i] = dx;
+ y[3][i] = i == k + 1 ? dx
+ : prev_dx + ( 1.0 / k ) * ( dx - prev_dx );
+ prev_dx = y[3][i];
+ }
+ }
+
+ return 0;
+}
+
diff --git a/lib/alligator.c b/lib/alligator.c
new file mode 100644
index 0000000..81450b0
--- /dev/null
+++ b/lib/alligator.c
@@ -0,0 +1,70 @@
+// Alligator Indicator
+//
+// William Elder's Alligator uses three SMAs shifted forward in time:
+// Jaw: SMA(13) shifted 8 bars
+// Teeth: SMA(8) shifted 5 bars
+// Lips: SMA(5) shifted 3 bars
+//
+// X_n - 1
+// Y_n - 3
+// C_n - 0
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_alligator_p = { .n_inputs = 1, .n_outputs = 3, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_alligator_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 12 );
+}
+
+int ts_alligator(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_alligator_p );
+ (void)c;
+ if( n == 0 ) return -1;
+ /* Jaw: SMA(13) shifted 8 positions */
+ /* Teeth: SMA(8) shifted 5 positions */
+ /* Lips: SMA(5) shifted 3 positions */
+
+ /* Jaw: SMA13 of x[i-8..i+4], displayed at i */
+ for (uint_t i = 8; i - 8 + 13 <= n; ++i)
+ {
+ fp_t sum = 0;
+ for (uint_t j = 0; j < 13; ++j)
+ {
+ sum += x[0][i - 8 + j];
+ }
+ y[0][i] = sum / 13;
+ }
+
+ /* Teeth: SMA8 of x[i-5..i+2], displayed at i */
+ for (uint_t i = 5; i - 5 + 8 <= n; ++i)
+ {
+ fp_t sum = 0;
+ for (uint_t j = 0; j < 8; ++j)
+ {
+ sum += x[0][i - 5 + j];
+ }
+ y[1][i] = sum / 8;
+ }
+
+ /* Lips: SMA5 of x[i-3..i+1], displayed at i */
+ for (uint_t i = 3; i - 3 + 5 <= n; ++i)
+ {
+ fp_t sum = 0;
+ for (uint_t j = 0; j < 5; ++j)
+ {
+ sum += x[0][i - 3 + j];
+ }
+ y[2][i] = sum / 5;
+ }
+ return 0;
+}
+
diff --git a/lib/angle_deg_to_rad.c b/lib/angle_deg_to_rad.c
new file mode 100644
index 0000000..e6929fa
--- /dev/null
+++ b/lib/angle_deg_to_rad.c
@@ -0,0 +1,40 @@
+// Angle Degrees to Radians
+//
+// angle_deg_to_rad - convert degrees to radians
+//
+// y[t] = x[0][t] * PI / 180.0
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_angle_deg_to_rad_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_angle_deg_to_rad_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_angle_deg_to_rad(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_angle_deg_to_rad_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = x[0][i] * PI / 180.0;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/angle_normalize.c b/lib/angle_normalize.c
new file mode 100644
index 0000000..8784f2c
--- /dev/null
+++ b/lib/angle_normalize.c
@@ -0,0 +1,46 @@
+// Angle Normalize
+//
+// angle_normalize - normalize angle to [-PI, PI]
+//
+// y[t] = fmod( x[t], 2*PI ), adjusted to [-PI, PI]
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_angle_normalize_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_angle_normalize_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_angle_normalize(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_angle_normalize_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t r = fmod( x[0][i], 2.0 * PI );
+ if( r > PI )
+ r -= 2.0 * PI;
+ if( r < -PI )
+ r += 2.0 * PI;
+ y[0][i] = r;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/angle_rad_to_deg.c b/lib/angle_rad_to_deg.c
new file mode 100644
index 0000000..124da05
--- /dev/null
+++ b/lib/angle_rad_to_deg.c
@@ -0,0 +1,40 @@
+// Angle Radians to Degrees
+//
+// angle_rad_to_deg - convert radians to degrees
+//
+// y[t] = x[0][t] * 180.0 / PI
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_angle_rad_to_deg_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_angle_rad_to_deg_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_angle_rad_to_deg(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_angle_rad_to_deg_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = x[0][i] * 180.0 / PI;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/annualized_historical_volatility.c b/lib/annualized_historical_volatility.c
new file mode 100644
index 0000000..c0c88cf
--- /dev/null
+++ b/lib/annualized_historical_volatility.c
@@ -0,0 +1,61 @@
+// Annualized Historical Volatility
+//
+// annualized_historical_volatility - ahv
+//
+// y[t] = stddev(log(x[t-k..t])) * sqrt(252)
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 1
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_annualized_historical_volatility_params_t;
+
+ts_parameter_t const ts_annualized_historical_volatility_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_annualized_historical_volatility_params_t) };
+
+uint_t ts_annualized_historical_volatility_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_annualized_historical_volatility_params_t *)c)->k : 0 );
+}
+
+int ts_annualized_historical_volatility(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_annualized_historical_volatility_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_annualized_historical_volatility_params_t *)c)->k;
+ if (k == 0 || n <= k) return -1;
+
+ /* Compute log returns, then stddev, then annualize */
+ for (uint_t i = k; i < n; ++i)
+ {
+ fp_t sum = 0;
+ fp_t sum2 = 0;
+ uint_t count = 0;
+ for (uint_t j = i - k + 1; j <= i; ++j)
+ {
+ if (x[0][j - 1] <= 0 || x[0][j] <= 0) continue;
+ fp_t ret = log(x[0][j] / x[0][j - 1]);
+ sum += ret;
+ sum2 += ret * ret;
+ count++;
+ }
+ if (count == 0) { y[0][i] = 0; continue; }
+ fp_t mean = sum / count;
+ fp_t variance = sum2 / count - mean * mean;
+ if (variance < 0) variance = 0;
+ y[0][i] = sqrt(variance) * sqrt(252.0);
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/apo.c b/lib/apo.c
new file mode 100644
index 0000000..f888162
--- /dev/null
+++ b/lib/apo.c
@@ -0,0 +1,68 @@
+// Absolute Price Oscillator
+//
+// apo - APO
+//
+// Difference between fast and slow EMA: APO = EMA(fast) - EMA(slow)
+//
+// Inputs: 1
+// Outputs: 1 (Oscillator)
+// Constants: 2 (fast period, slow period)
+
+#include "ts.h"
+
+typedef struct {
+ fp_t fast_k;
+ fp_t slow_k;
+} ts_apo_params_t;
+
+ts_parameter_t const ts_apo_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 2, .params_size = sizeof(ts_apo_params_t) };
+
+uint_t ts_apo_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? (uint_t)((ts_apo_params_t *)c)->slow_k - 1 : 0 );
+}
+
+int ts_apo(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_apo_p );
+ if ( !c ) return -1;
+ if (n == 0) return -1;
+
+ fp_t fast_k = ((ts_apo_params_t *)c)->fast_k;
+ fp_t slow_k = ((ts_apo_params_t *)c)->slow_k;
+ if (fast_k == 0 || slow_k == 0) return -1;
+
+ fp_t fast_a = 1.0 / fast_k;
+ fp_t slow_a = 1.0 / slow_k;
+
+ uint_t slow_offset = (uint_t)(slow_k - 1);
+ if (slow_offset >= n) return -1;
+
+ fp_t fast_ema = x[0][0];
+ fp_t slow_ema = x[0][0];
+
+ for( uint_t i = 1; i <= slow_offset; ++i )
+ {
+ fast_ema = fast_ema + fast_a * ( x[0][i] - fast_ema );
+ slow_ema = slow_ema + slow_a * ( x[0][i] - slow_ema );
+ }
+
+ y[0][slow_offset] = fast_ema - slow_ema;
+
+ for( uint_t i = slow_offset + 1; i < n; ++i )
+ {
+ fast_ema = fast_ema + fast_a * ( x[0][i] - fast_ema );
+ slow_ema = slow_ema + slow_a * ( x[0][i] - slow_ema );
+
+ y[0][i] = fast_ema - slow_ema;
+ }
+
+ return 0;
+}
+
+
diff --git a/lib/aroon.c b/lib/aroon.c
new file mode 100644
index 0000000..539e97b
--- /dev/null
+++ b/lib/aroon.c
@@ -0,0 +1,67 @@
+// Aroon
+//
+// aroon - ar
+//
+// Measures the time since the highest high and lowest low in a lookback window.
+// Aroon Up = ((period - days since highest high) / period) * 100
+// Aroon Down = ((period - days since lowest low) / period) * 100
+//
+// Inputs: 4 (OHLC)
+// Outputs: 2 (Aroon Up, Aroon Down)
+// Constants: 1 (period)
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_aroon_params_t;
+
+ts_parameter_t const ts_aroon_p = { .n_inputs = 4, .n_outputs = 2, .n_constants = 1, .params_size = sizeof(ts_aroon_params_t) };
+
+uint_t ts_aroon_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_aroon_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_aroon(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_aroon_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_aroon_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for ( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t highest_high = ts_h( x, i - k + 1 );
+ uint_t age_highest = 0;
+ for ( uint_t j = i - k + 1; j <= i; ++j )
+ {
+ if ( ts_h( x, j ) > highest_high )
+ {
+ highest_high = ts_h( x, j );
+ age_highest = i - j;
+ }
+ }
+
+ fp_t lowest_low = ts_l( x, i - k + 1 );
+ uint_t age_lowest = 0;
+ for ( uint_t j = i - k + 1; j <= i; ++j )
+ {
+ if ( ts_l( x, j ) < lowest_low )
+ {
+ lowest_low = ts_l( x, j );
+ age_lowest = i - j;
+ }
+ }
+
+ y[0][i] = ( ( fp_t )( k - age_highest ) / ( fp_t )k ) * 100.0;
+ y[1][i] = ( ( fp_t )( k - age_lowest ) / ( fp_t )k ) * 100.0;
+ }
+ return 0;
+}
+
diff --git a/lib/aroon_oscillator.c b/lib/aroon_oscillator.c
new file mode 100644
index 0000000..db9fe9d
--- /dev/null
+++ b/lib/aroon_oscillator.c
@@ -0,0 +1,71 @@
+// Aroon Oscillator
+//
+// aroon_oscillator - aro
+//
+// The difference between Aroon Up and Aroon Down.
+// Aroon Oscillator = Aroon Up - Aroon Down
+//
+// Inputs: 4 (OHLC)
+// Outputs: 1 (Oscillator)
+// Constants: 1 (period)
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_aroon_oscillator_params_t;
+
+ts_parameter_t const ts_aroon_oscillator_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_aroon_oscillator_params_t) };
+
+uint_t ts_aroon_oscillator_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_aroon_oscillator_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_aroon_oscillator(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_aroon_oscillator_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_aroon_oscillator_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for ( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t highest_high = ts_h( x, i - k + 1 );
+ uint_t age_highest = 0;
+ for ( uint_t j = i - k + 1; j <= i; ++j )
+ {
+ if ( ts_h( x, j ) > highest_high )
+ {
+ highest_high = ts_h( x, j );
+ age_highest = i - j;
+ }
+ }
+
+ fp_t lowest_low = ts_l( x, i - k + 1 );
+ uint_t age_lowest = 0;
+ for ( uint_t j = i - k + 1; j <= i; ++j )
+ {
+ if ( ts_l( x, j ) < lowest_low )
+ {
+ lowest_low = ts_l( x, j );
+ age_lowest = i - j;
+ }
+ }
+
+ fp_t aroon_up = ( ( fp_t )( k - age_highest ) / ( fp_t )k ) * 100.0;
+ fp_t aroon_dn = ( ( fp_t )( k - age_lowest ) / ( fp_t )k ) * 100.0;
+
+ y[0][i] = aroon_up - aroon_dn;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/asi.c b/lib/asi.c
new file mode 100644
index 0000000..ac4225b
--- /dev/null
+++ b/lib/asi.c
@@ -0,0 +1,53 @@
+// Accumulation Swing Index
+//
+// asi - ASI
+//
+// Cumulative swing indicator using OHLC data to measure buying/selling pressure
+//
+// Inputs: 4 (OHLC)
+// Outputs: 1 (ASI)
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_asi_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_asi_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_asi(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_asi_p );
+ (void)c;
+ if( n == 0 ) return -1;
+
+ y[0][0] = 0;
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ fp_t term1 = ts_c( x, i ) - ts_c( x, i - 1 );
+ fp_t term2 = 0.5 * ( ts_o( x, i ) - ts_o( x, i - 1 ) );
+ fp_t term3 = 0.25 * ( ts_c( x, i ) - ts_o( x, i ) + ts_o( x, i - 1 ) - ts_c( x, i - 1 ) );
+ fp_t denom = fmax( ts_h( x, i ) - ts_l( x, i - 1 ),
+ fmax( ts_h( x, i - 1 ) - ts_l( x, i ),
+ ts_h( x, i - 1 ) - ts_l( x, i - 1 ) ) );
+ fp_t component2 = denom != 0 ? ( term1 - term2 + term3 ) / denom * ( ts_h( x, i ) - ts_l( x, i ) ) : 0;
+ fp_t half_denom = 0.5 * denom;
+ fp_t component3 = half_denom != 0 ? ( term1 - term2 + term3 ) / half_denom * 0.5 : 0;
+
+ y[0][i] = y[0][i - 1] + component2 + component3;
+ }
+
+ return 0;
+}
+
+
diff --git a/lib/asinh.c b/lib/asinh.c
new file mode 100644
index 0000000..992dbcc
--- /dev/null
+++ b/lib/asinh.c
@@ -0,0 +1,42 @@
+// Hyperbolic Arcsine
+//
+// asinh - inverse hyperbolic sine
+//
+// y[t] = asinh( x[t] )
+// = ln( x + √( x^2 + 1 ) )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_asinh_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_asinh_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_asinh(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_asinh_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = asinh( x[0][i] );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/atan2.c b/lib/atan2.c
new file mode 100644
index 0000000..5242cb7
--- /dev/null
+++ b/lib/atan2.c
@@ -0,0 +1,42 @@
+// Atan2
+//
+// atan2 - four-quadrant arctangent
+//
+// y[t] = atan2( x[0][t], x[1][t] )
+//
+// X_n - 2
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_atan2_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_atan2_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_atan2(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_atan2_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = atan2( x[0][i], x[1][i] );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/atanh.c b/lib/atanh.c
new file mode 100644
index 0000000..06896df
--- /dev/null
+++ b/lib/atanh.c
@@ -0,0 +1,50 @@
+// Hyperbolic Arctangent
+//
+// atanh - inverse hyperbolic tangent
+//
+// y[t] = atanh( x[t] ) if |x[t]| < 1
+// y[t] = +INFINITY if x[t] == 1
+// y[t] = -INFINITY if x[t] == -1
+// y[t] = NAN if |x[t]| > 1
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_atanh_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_atanh_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_atanh(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_atanh_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t v = x[0][i];
+ if ( v > -1.0 && v < 1.0 )
+ y[0][i] = atanh( v );
+ else if ( v == 1.0 )
+ y[0][i] = INFINITY;
+ else if ( v == -1.0 )
+ y[0][i] = -INFINITY;
+ else
+ y[0][i] = (fp_t)NAN;
+ }
+ return 0;
+}
+
diff --git a/lib/atr.c b/lib/atr.c
new file mode 100644
index 0000000..7c9f06f
--- /dev/null
+++ b/lib/atr.c
@@ -0,0 +1,73 @@
+// Average True Range
+//
+// atr - ATR
+//
+// Welles Wilder's Average True Range. True Range is the maximum of:
+// (1) High - Low
+// (2) |High - Previous Close|
+// (3) |Low - Previous Close|
+// The first ATR is the SMA of the first k TR values; subsequent values
+// use Wilder's smoothing: ATR = (prev_ATR * (k-1) + TR) / k
+//
+// Inputs: 4 (OHLC)
+// Outputs: 1 (ATR)
+// Constants: 1 (period)
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_atr_params_t;
+
+ts_parameter_t const ts_atr_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_atr_params_t) };
+
+
+uint_t ts_atr_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_atr_params_t *)c)->k : 0 );
+}
+
+int ts_atr(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_atr_p );
+ if ( !c ) return -1;
+ TS_CHECK_INPUT(x, x_n, n);
+ uint_t k = ((ts_atr_params_t *)c)->k;
+ if (k == 0) return -1;
+ if (n < 2) return -1;
+
+ fp_t tr_sum = 0;
+
+ /* Cumulative SMA for first k TR values */
+ for( uint_t i = 1; i < k && i < n; ++i )
+ {
+ fp_t tr = fmax( ts_h( x, i ) - ts_l( x, i ), fmax( fabs( ts_h( x, i ) - ts_c( x, i - 1 ) ), fabs( ts_l( x, i ) - ts_c( x, i - 1 ) ) ) );
+ tr_sum += tr;
+ y[0][i] = tr_sum / (fp_t)i;
+ }
+
+ /* At bar k, use SMA of first k TR values */
+ if( k < n )
+ {
+ fp_t tr = fmax( ts_h( x, k ) - ts_l( x, k ), fmax( fabs( ts_h( x, k ) - ts_c( x, k - 1 ) ), fabs( ts_l( x, k ) - ts_c( x, k - 1 ) ) ) );
+ tr_sum += tr;
+ y[0][k] = tr_sum / (fp_t)k;
+ }
+
+ /* Wilder's smoothing from bar k+1 */
+ for( uint_t i = k + 1; i < n; ++i )
+ {
+ fp_t tr = fmax( ts_h( x, i ) - ts_l( x, i ), fmax( fabs( ts_h( x, i ) - ts_c( x, i - 1 ) ), fabs( ts_l( x, i ) - ts_c( x, i - 1 ) ) ) );
+ y[0][i] = ( y[0][i - 1] * ( k - 1 ) + tr ) / k;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/atr_bands.c b/lib/atr_bands.c
new file mode 100644
index 0000000..73b7d44
--- /dev/null
+++ b/lib/atr_bands.c
@@ -0,0 +1,87 @@
+// ATR Bands
+//
+// atr_bands - ATR bands
+//
+// Upper: SMA(Close) + mult * ATR
+// Middle: SMA(Close)
+// Lower: SMA(Close) - mult * ATR
+//
+// Inputs: 4 (OHLC)
+// Outputs: 3 (Upper, Middle, Lower)
+// Constants: 2 (period, multiplier)
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+ fp_t mult;
+} ts_atr_bands_params_t;
+
+ts_parameter_t const ts_atr_bands_p = { .n_inputs = 4, .n_outputs = 3, .n_constants = 2, .params_size = sizeof(ts_atr_bands_params_t) };
+
+
+uint_t ts_atr_bands_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_atr_bands_params_t *)c)->k : 0 );
+}
+
+int ts_atr_bands(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_atr_bands_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_atr_bands_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+ if (n < 2) return -1;
+
+ fp_t mult = ((ts_atr_bands_params_t *)c)->mult;
+
+ fp_t tr_sum = 0;
+ fp_t atr = 0;
+
+ // Accumulate ATR from i=1, but only write output from i=k-1 (when SMA window is valid)
+ for( uint_t i = 1; i < n; ++i )
+ {
+ fp_t tr = fmax( ts_h( x, i ) - ts_l( x, i ), fmax( fabs( ts_h( x, i ) - ts_c( x, i - 1 ) ), fabs( ts_l( x, i ) - ts_c( x, i - 1 ) ) ) );
+
+ if( i < k )
+ {
+ tr_sum += tr;
+ atr = tr_sum / (fp_t)i;
+ }
+ else if( i == k )
+ {
+ tr_sum += tr;
+ atr = tr_sum / (fp_t)k;
+ }
+ else
+ {
+ atr = ( atr * ( k - 1 ) + tr ) / k;
+ }
+
+ // Only write output when SMA window is fully within bounds (i >= k-1)
+ if( i >= k - 1 )
+ {
+ fp_t sum = 0;
+ for( uint_t j = 0; j < k; ++j )
+ {
+ sum += ts_c( x, i - k + 1 + j );
+ }
+ fp_t mean = sum / k;
+
+ y[0][i] = mean + mult * atr;
+ y[1][i] = mean;
+ y[2][i] = mean - mult * atr;
+ }
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/atr_trailing_stops.c b/lib/atr_trailing_stops.c
new file mode 100644
index 0000000..2c7cd73
--- /dev/null
+++ b/lib/atr_trailing_stops.c
@@ -0,0 +1,88 @@
+// ATR Trailing Stops
+//
+// atr_trailing_stops - ATR stops
+//
+// Upper stop: max(prev, (H+L)/2 + mult*ATR)
+// Lower stop: min(prev, (H+L)/2 - mult*ATR)
+//
+// Inputs: 4 (OHLC)
+// Outputs: 2 (Upper Stop, Lower Stop)
+// Constants: 2 (period, multiplier)
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+ fp_t mult;
+} ts_atr_trailing_stops_params_t;
+
+ts_parameter_t const ts_atr_trailing_stops_p = { .n_inputs = 4, .n_outputs = 2, .n_constants = 2, .params_size = sizeof(ts_atr_trailing_stops_params_t) };
+
+
+uint_t ts_atr_trailing_stops_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_atr_trailing_stops_params_t *)c)->k : 0 );
+}
+
+int ts_atr_trailing_stops(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_atr_trailing_stops_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_atr_trailing_stops_params_t *)c)->k;
+ if (k == 0) return -1;
+ if (n < k + 1) return -1;
+
+ fp_t mult = ((ts_atr_trailing_stops_params_t *)c)->mult;
+
+ fp_t tr_sum = 0;
+ fp_t atr = 0;
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ fp_t tr = fmax( ts_h( x, i ) - ts_l( x, i ), fmax( fabs( ts_h( x, i ) - ts_c( x, i - 1 ) ), fabs( ts_l( x, i ) - ts_c( x, i - 1 ) ) ) );
+
+ if( i < k )
+ {
+ tr_sum += tr;
+ atr = tr_sum / (fp_t)i;
+ }
+ else if( i == k )
+ {
+ tr_sum += tr;
+ atr = tr_sum / (fp_t)k;
+ }
+ else
+ {
+ atr = ( atr * ( k - 1 ) + tr ) / k;
+ }
+
+ if( i >= k )
+ {
+ fp_t mid = ( ts_h( x, i ) + ts_l( x, i ) ) / 2.0;
+ fp_t calc_upper = mid + mult * atr;
+ fp_t calc_lower = mid - mult * atr;
+
+ if( i == k )
+ {
+ y[0][i] = calc_upper;
+ y[1][i] = calc_lower;
+ }
+ else
+ {
+ y[0][i] = fmax( y[0][i - 1], calc_upper );
+ y[1][i] = fmin( y[1][i - 1], calc_lower );
+ }
+ }
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/average.c b/lib/average.c
new file mode 100644
index 0000000..391de93
--- /dev/null
+++ b/lib/average.c
@@ -0,0 +1,71 @@
+// Simple Moving Average
+//
+// average_simple - sma
+//
+// Unweighted mean of previous k data points
+//
+// SMA_k = ( 1 / k ) * SUM[ i = 1, k ] x_i
+//
+// Initial Values
+//
+// Cumulative moving average to n = k
+//
+// Successive Value
+//
+// SMA_n = x_n-1 + ( 1 / n )( x_n - x_n-1 )
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+} ts_average_params_t;
+
+ts_parameter_t const ts_average_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_average_params_t) };
+
+
+uint_t ts_average_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_average_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_average(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_average_p );
+ if ( !c ) return -1;
+ TS_CHECK_INPUT(x, x_n, n);
+ uint_t k = ((ts_average_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ uint_t xi = 0;
+ uint_t yi = k;
+
+ fp_t sum = 0;
+
+ for( ; xi < k; ++xi )
+ {
+ sum += x[0][xi];
+ }
+
+ y[0][k - 1] = sum / k;
+
+ for( ; xi < n; ++xi, ++yi )
+ {
+ y[0][yi] = y[0][yi - 1] + ( x[0][xi] - x[0][xi - k] ) / k;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/average_cumulative.c b/lib/average_cumulative.c
new file mode 100644
index 0000000..905ccfe
--- /dev/null
+++ b/lib/average_cumulative.c
@@ -0,0 +1,47 @@
+// Cumulative Moving Average
+//
+// average_cumulative - cma
+//
+// Average of all data to the current point
+//
+// y[t] = ( 1 / k ) * SUM[ t = 1, k ]( x[t] )
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_average_cumulative_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_average_cumulative_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_average_cumulative(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_average_cumulative_p );
+ (void)c;
+ fp_t sum = 0;
+
+ for( uint_t i = 0; i < n; ++i )
+ {
+ sum += x[0][i];
+ y[0][i] = sum / ( i + 1 );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/average_exponential.c b/lib/average_exponential.c
new file mode 100644
index 0000000..bae9d5f
--- /dev/null
+++ b/lib/average_exponential.c
@@ -0,0 +1,56 @@
+// Exponential Moving Average
+//
+// average_exponential - ema
+//
+// Weighted moving average with exponentially decreasing weights determined by
+// the constant c
+//
+// y[0] = x[0]
+// y[t] = a * x[t] + ( 1 - a ) * EMA[t - 1]
+// a = 1 / k;
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ fp_t k;
+} ts_average_exponential_params_t;
+
+ts_parameter_t const ts_average_exponential_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_average_exponential_params_t) };
+
+
+uint_t ts_average_exponential_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_average_exponential(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_average_exponential_p );
+ if ( !c ) return -1;
+ TS_CHECK_INPUT(x, x_n, n);
+ fp_t k = ((ts_average_exponential_params_t *)c)->k;
+ if (k <= 0 || isnan(k)) return -1;
+
+ fp_t a = 1.0 / k;
+
+ y[0][0] = x[0][0];
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ y[0][i] = y[0][i - 1] + a * ( x[0][i] - y[0][i - 1] );
+ }
+ return 0;
+}
+
diff --git a/lib/average_exponential_double.c b/lib/average_exponential_double.c
new file mode 100644
index 0000000..eeb890f
--- /dev/null
+++ b/lib/average_exponential_double.c
@@ -0,0 +1,57 @@
+// Double Exponential Moving Average
+//
+// average_exponential_double - dema
+//
+// y[i] =
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ fp_t a;
+} ts_average_exponential_double_params_t;
+
+ts_parameter_t const ts_average_exponential_double_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_average_exponential_double_params_t) };
+
+
+uint_t ts_average_exponential_double_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_average_exponential_double(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_average_exponential_double_p );
+ if ( !c ) return -1;
+ TS_CHECK_INPUT(x, x_n, n);
+ fp_t a = ((ts_average_exponential_double_params_t *)c)->a;
+ if (a == 0 || n == 0) return -1;
+
+ fp_t ema1 = x[0][0];
+ fp_t ema2 = x[0][0];
+
+ y[0][0] = x[0][0];
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ ema1 = ema1 + a * ( x[0][i] - ema1 );
+ ema2 = ema2 + a * ( ema1 - ema2 );
+
+ y[0][i] = 2 * ema1 - ema2;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/average_exponential_triple.c b/lib/average_exponential_triple.c
new file mode 100644
index 0000000..467d87f
--- /dev/null
+++ b/lib/average_exponential_triple.c
@@ -0,0 +1,57 @@
+// Triple Exponential Moving Average
+//
+// average_exponential_triple - tema
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ fp_t a;
+} ts_average_exponential_triple_params_t;
+
+ts_parameter_t const ts_average_exponential_triple_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_average_exponential_triple_params_t) };
+
+
+uint_t ts_average_exponential_triple_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_average_exponential_triple(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_average_exponential_triple_p );
+ if ( !c ) return -1;
+ TS_CHECK_INPUT(x, x_n, n);
+ fp_t a = ((ts_average_exponential_triple_params_t *)c)->a;
+ if (a == 0 || n == 0) return -1;
+
+ fp_t ema1 = x[0][0];
+ fp_t ema2 = x[0][0];
+ fp_t ema3 = x[0][0];
+
+ y[0][0] = x[0][0];
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ ema1 = ema1 + a * ( x[0][i] - ema1 );
+ ema2 = ema2 + a * ( ema1 - ema2 );
+ ema3 = ema3 + a * ( ema2 - ema3 );
+
+ y[0][i] = 3 * ema1 - 3 * ema2 + ema3;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/average_price.c b/lib/average_price.c
new file mode 100644
index 0000000..8c270b8
--- /dev/null
+++ b/lib/average_price.c
@@ -0,0 +1,39 @@
+// Average Price
+//
+// average_price - avg_price
+//
+// Average of high, low, and close: (H + L + C) / 3
+//
+// Inputs: 5 (OHLCV)
+// Outputs: 1 (Average Price)
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_average_price_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_average_price_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_average_price(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_average_price_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = ( x[1][i] + x[2][i] + x[3][i] ) / 3;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/average_triangle.c b/lib/average_triangle.c
new file mode 100644
index 0000000..9ac952d
--- /dev/null
+++ b/lib/average_triangle.c
@@ -0,0 +1,107 @@
+// Triangular Moving Average
+//
+// average_triangle - tma
+//
+// A moving average of moving averages
+//
+// y[i] =
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+//
+// 012345678901234567890123456789
+// ---------01234567890123456
+// ------------------0
+
+
+#include <stdlib.h>
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+} ts_average_triangle_params_t;
+
+ts_parameter_t const ts_average_triangle_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_average_triangle_params_t) };
+
+
+uint_t ts_average_triangle_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? 2 * ( ((ts_average_triangle_params_t *)c)->k - 1 ) : 0 );
+}
+
+int ts_average_triangle(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_average_triangle_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_average_triangle_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ fp_t rk2 = _ts_reciprocal( _ts_square( k ) );
+ uint_t xi = 0;
+ uint_t yi = 2 * ( k - 1 );
+
+ fp_t *xsum = malloc(k * sizeof(fp_t));
+ if (!xsum) return -1;
+ uint_t xsumi = 0;
+
+ fp_t sum_of_xsum = 0;
+
+ xsum[0] = 0;
+
+ for (uint_t j = 0; j < n && j < 2 * (k - 1); ++j) y[0][j] = 0;
+
+ fp_t last_sum = 0;
+
+ for( ; xi < k; xi++ )
+ {
+ xsum[xsumi] += x[0][xi];
+ last_sum = xsum[xsumi];
+ }
+
+ sum_of_xsum = xsum[xsumi];
+ xsumi++;
+
+ for( ; xi < 2 * k - 1; xi++ )
+ {
+ xsum[xsumi] = xsum[xsumi - 1] + x[0][xi] - x[0][xi - k];
+ last_sum = xsum[xsumi];
+ sum_of_xsum += xsum[xsumi];
+ xsumi++;
+ }
+
+ y[0][yi++] = sum_of_xsum * rk2;
+
+ for( ; xi < n; ++xi, ++yi )
+ {
+ if( xsumi == k )
+ {
+ xsumi = 0;
+ sum_of_xsum -= xsum[xsumi];
+ xsum[xsumi] = last_sum + x[0][xi] - x[0][xi - k];
+ }
+ else
+ {
+ sum_of_xsum -= xsum[xsumi];
+ xsum[xsumi] = last_sum + x[0][xi] - x[0][xi - k];
+ }
+
+ last_sum = xsum[xsumi];
+ sum_of_xsum += xsum[xsumi];
+ y[0][yi] = sum_of_xsum * rk2;
+
+ xsumi++;
+ }
+
+ free(xsum);
+ return 0;
+}
+
+
+
diff --git a/lib/average_weighted.c b/lib/average_weighted.c
new file mode 100644
index 0000000..eb99a17
--- /dev/null
+++ b/lib/average_weighted.c
@@ -0,0 +1,67 @@
+// Weighted Moving Average
+//
+// average_weighted - wma
+//
+// Weighted mean of previous k data points
+//
+// y[t] = SUM[ i = t, t - k]( x[t] * ( ) )
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+} ts_average_weighted_params_t;
+
+ts_parameter_t const ts_average_weighted_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_average_weighted_params_t) };
+
+
+uint_t ts_average_weighted_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_average_weighted_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_average_weighted(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_average_weighted_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_average_weighted_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ uint_t xi = 0;
+ uint_t yi = k;
+
+ fp_t sum = 0;
+ fp_t wsum = 0;
+
+ for( ; xi < k; ++xi )
+ {
+ sum += x[0][xi];
+ wsum += x[0][xi] * ( xi + 1 );
+ }
+
+ fp_t rtri = 1.0 / _ts_triangle_number( k );
+
+ y[0][k - 1] = wsum * rtri;
+
+ for( ; xi < n; ++xi, ++yi )
+ {
+ wsum = wsum + ( k * x[0][xi] ) - sum;
+ sum = sum + x[0][xi] - x[0][xi - k];
+ y[0][yi] = wsum * rtri;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/average_weighted_series.c b/lib/average_weighted_series.c
new file mode 100644
index 0000000..d7e2ccb
--- /dev/null
+++ b/lib/average_weighted_series.c
@@ -0,0 +1,78 @@
+// Average Weighted Series
+//
+// average_weighted_series - aws
+//
+// SMA of the product of two series: SMA(x0 * x1, k)
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1 (period)
+
+#include <math.h>
+#include <stdlib.h>
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+} ts_average_weighted_series_params_t;
+
+ts_parameter_t const ts_average_weighted_series_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_average_weighted_series_params_t) };
+
+
+uint_t ts_average_weighted_series_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_average_weighted_series_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_average_weighted_series(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_average_weighted_series_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_average_weighted_series_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ uint_t xi = 0;
+ uint_t yi = k;
+
+ fp_t *b = malloc(k * sizeof(fp_t));
+ if (!b) return -1;
+ uint_t bi = 0;
+
+ fp_t sum = 0;
+
+ while( xi < k )
+ {
+ b[bi] = x[0][xi] * x[1][xi];
+ sum += b[bi];
+
+ ++xi;
+ bi = _ts_circle_increment( bi, k );
+ }
+
+ y[0][k - 1] = sum / k;
+ uint_t tmp;
+
+ while( xi < n )
+ {
+ tmp = b[bi];
+ b[bi] = x[0][xi] * x[1][xi];
+
+ y[0][yi] = y[0][yi - 1] + ( b[bi] - tmp ) / k;
+
+ ++xi;
+ ++yi;
+ bi = _ts_circle_increment( bi, k );
+ }
+
+ free(b);
+ return 0;
+}
+
+
+
diff --git a/lib/awesome_oscillator.c b/lib/awesome_oscillator.c
new file mode 100644
index 0000000..be1932e
--- /dev/null
+++ b/lib/awesome_oscillator.c
@@ -0,0 +1,66 @@
+// Awesome Oscillator
+//
+// awesome_oscillator - AO
+//
+// EMA5(MP) - EMA34(MP), where MP = (H + L) / 2
+//
+// Inputs: 4 (OHLC)
+// Outputs: 1 (Oscillator)
+// Constants: 0
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_awesome_oscillator_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_awesome_oscillator_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 33 );
+}
+
+int ts_awesome_oscillator(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_awesome_oscillator_p );
+ (void)c;
+ if (n < 34) return -1;
+
+ uint_t fast_period = 5;
+ uint_t slow_period = 34;
+
+ fp_t fast_a = 2.0 / ( ( fp_t )fast_period + 1.0 );
+ fp_t slow_a = 2.0 / ( ( fp_t )slow_period + 1.0 );
+
+ fp_t sum_fast = 0;
+ fp_t sum_slow = 0;
+
+ for ( uint_t j = 0; j < slow_period; ++j )
+ {
+ fp_t mp = ( ts_h( x, j ) + ts_l( x, j ) ) / 2.0;
+ sum_slow += mp;
+ if ( j < fast_period )
+ sum_fast += mp;
+ }
+
+ fp_t ema_fast = sum_fast / ( fp_t )fast_period;
+ fp_t ema_slow = sum_slow / ( fp_t )slow_period;
+
+ for ( uint_t i = 0; i < slow_period - 1; ++i )
+ y[0][i] = 0;
+
+ y[0][slow_period - 1] = ema_fast - ema_slow;
+
+ for( uint_t i = slow_period; i < n; ++i )
+ {
+ fp_t mp = ( ts_h( x, i ) + ts_l( x, i ) ) / 2.0;
+ ema_fast = ema_fast + fast_a * ( mp - ema_fast );
+ ema_slow = ema_slow + slow_a * ( mp - ema_slow );
+ y[0][i] = ema_fast - ema_slow;
+ }
+ return 0;
+}
+
diff --git a/lib/balance_of_power.c b/lib/balance_of_power.c
new file mode 100644
index 0000000..26f3456
--- /dev/null
+++ b/lib/balance_of_power.c
@@ -0,0 +1,41 @@
+// Balance of Power
+//
+// balance_of_power - BOP
+//
+// Measures relationship between buyers and sellers: (C - O) / (H - L)
+//
+// Inputs: 4 (OHLC)
+// Outputs: 1 (BOP)
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_balance_of_power_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_balance_of_power_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_balance_of_power(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_balance_of_power_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t hl = ts_h( x, i ) - ts_l( x, i );
+ y[0][i] = hl != 0 ? ( ts_c( x, i ) - ts_o( x, i ) ) / hl : 0;
+ }
+
+ return 0;
+}
+
+
+
diff --git a/lib/band_width.c b/lib/band_width.c
new file mode 100644
index 0000000..822d835
--- /dev/null
+++ b/lib/band_width.c
@@ -0,0 +1,60 @@
+// Band Width
+//
+// band_width - BW
+//
+// Normalized width of Bollinger Bands: (Upper - Lower) / SMA * 100
+//
+// Inputs: 1
+// Outputs: 1 (Width %)
+// Constants: 2 (period, std multiplier)
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+ fp_t std_mult;
+} ts_band_width_params_t;
+
+ts_parameter_t const ts_band_width_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 2, .params_size = sizeof(ts_band_width_params_t) };
+
+uint_t ts_band_width_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_band_width_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_band_width(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_band_width_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_band_width_params_t *)c)->k;
+ fp_t std_mult = ((ts_band_width_params_t *)c)->std_mult;
+ if (k == 0 || n < k) return -1;
+
+ for (uint_t i = k - 1; i < n; ++i)
+ {
+ fp_t sum = 0;
+ fp_t sum2 = 0;
+ for (uint_t j = i - k + 1; j <= i; ++j)
+ {
+ sum += x[0][j];
+ sum2 += x[0][j] * x[0][j];
+ }
+ fp_t mean = sum / k;
+ fp_t variance = sum2 / k - mean * mean;
+ if (variance < 0) variance = 0;
+ fp_t stddev = sqrt(variance);
+ fp_t upper = mean + std_mult * stddev;
+ fp_t lower = mean - std_mult * stddev;
+ y[0][i] = mean != 0 ? (upper - lower) / mean * 100 : 0;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/beta_pdf.c b/lib/beta_pdf.c
new file mode 100644
index 0000000..a23ed68
--- /dev/null
+++ b/lib/beta_pdf.c
@@ -0,0 +1,52 @@
+// Beta PDF
+//
+// beta_pdf - beta probability density
+//
+// f(x; α, β) = x^(α-1) * (1-x)^(β-1) / B(α, β), for 0 < x < 1
+//
+// Inputs: 1
+// Outputs: 1 (PDF value)
+// Constants: 2 (alpha, beta)
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ fp_t alpha;
+ fp_t beta;
+} ts_beta_pdf_params_t;
+
+ts_parameter_t const ts_beta_pdf_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 2, .params_size = sizeof(ts_beta_pdf_params_t) };
+
+uint_t ts_beta_pdf_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_beta_pdf(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_beta_pdf_p );
+ if ( !c ) return -1;
+ fp_t alpha = ((ts_beta_pdf_params_t *)c)->alpha;
+ fp_t beta = ((ts_beta_pdf_params_t *)c)->beta;
+ if (isnan(alpha) || isnan(beta) || alpha <= 0 || beta <= 0)
+ {
+ for( uint_t i = 0; i < n; ++i ) y[0][i] = 0;
+ return -1;
+ }
+ fp_t beta_fn = exp( lgamma( alpha ) + lgamma( beta ) - lgamma( alpha + beta ) );
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xi = x[0][i];
+ y[0][i] = ( xi > 0 && xi < 1 ) ? pow( xi, alpha - 1.0 ) * pow( 1.0 - xi, beta - 1.0 ) / beta_fn : 0.0;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/binomial.c b/lib/binomial.c
new file mode 100644
index 0000000..9b50698
--- /dev/null
+++ b/lib/binomial.c
@@ -0,0 +1,58 @@
+// Binomial Coefficient
+//
+// binomial - C(n, k)
+//
+// Number of combinations: C(n, k) = n! / (k! * (n-k)!)
+//
+// Inputs: 2 (n, k)
+// Outputs: 1 (coefficient)
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_binomial_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_binomial_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_binomial(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_binomial_p );
+ (void)c;
+ /* C(n, k) = n! / (k! * (n-k)!) */
+ for (uint_t i = 0; i < n; ++i)
+ {
+ fp_t n_val = floor(x[0][i]);
+ fp_t k_val = floor(x[1][i]);
+
+ if (k_val > n_val || k_val < 0 || n_val < 0)
+ {
+ y[0][i] = 0;
+ continue;
+ }
+
+ /* Use symmetry: C(n,k) = C(n, n-k) */
+ if (k_val > n_val - k_val)
+ k_val = n_val - k_val;
+
+ fp_t result = 1;
+ for (uint_t j = 0; j < (uint_t)k_val; ++j)
+ {
+ result = result * (n_val - j) / (j + 1);
+ }
+ y[0][i] = result;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/binomial_pdf.c b/lib/binomial_pdf.c
new file mode 100644
index 0000000..8b3a442
--- /dev/null
+++ b/lib/binomial_pdf.c
@@ -0,0 +1,63 @@
+// Binomial PDF
+//
+// binomial_pdf - binomial probability density
+//
+// P(X=k) = C(n, k) * p^k * (1-p)^(n-k)
+//
+// Inputs: 1 (k values)
+// Outputs: 1 (PDF value)
+// Constants: 1 (n trials, p probability)
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t n_trials;
+ fp_t p;
+} ts_binomial_pdf_params_t;
+
+ts_parameter_t const ts_binomial_pdf_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_binomial_pdf_params_t) };
+
+uint_t ts_binomial_pdf_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_binomial_pdf(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_binomial_pdf_p );
+ if ( !c ) return -1;
+ uint_t n_trials = ((ts_binomial_pdf_params_t *)c)->n_trials;
+ fp_t p = ((ts_binomial_pdf_params_t *)c)->p;
+ if (p < 0.0 || p > 1.0 || isnan(p) || isinf(p)) return -1;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ uint_t k = (uint_t)x[0][i];
+ if( k > n_trials )
+ {
+ y[0][i] = 0.0;
+ }
+ else if( p == 0.0 )
+ {
+ y[0][i] = k == 0 ? 1.0 : 0.0;
+ }
+ else if( p == 1.0 )
+ {
+ y[0][i] = k == n_trials ? 1.0 : 0.0;
+ }
+ else
+ {
+ fp_t log_comb = lgamma( n_trials + 1.0 ) - lgamma( k + 1.0 ) - lgamma( n_trials - k + 1.0 );
+ y[0][i] = exp( log_comb + k * log( p ) + ( n_trials - k ) * log( 1.0 - p ) );
+ }
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/bollinger_bands.c b/lib/bollinger_bands.c
new file mode 100644
index 0000000..55da054
--- /dev/null
+++ b/lib/bollinger_bands.c
@@ -0,0 +1,88 @@
+// Bollinger Bands
+//
+// bollinger_bands - BB
+//
+// Upper: SMA + mult * stddev
+// Middle: SMA
+// Lower: SMA - mult * stddev
+//
+// Inputs: 1
+// Outputs: 3 (Upper, Middle, Lower)
+// Constants: 2 (period, std multiplier)
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+ fp_t std_mult;
+} ts_bollinger_bands_params_t;
+
+ts_parameter_t const ts_bollinger_bands_p = { .n_inputs = 1, .n_outputs = 3, .n_constants = 2, .params_size = sizeof(ts_bollinger_bands_params_t) };
+
+
+uint_t ts_bollinger_bands_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_bollinger_bands_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_bollinger_bands(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_bollinger_bands_p );
+ if ( !c ) return -1;
+ TS_CHECK_INPUT(x, x_n, n);
+ uint_t k = ((ts_bollinger_bands_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+ fp_t std_mult = ((ts_bollinger_bands_params_t *)c)->std_mult;
+ if (std_mult < 0 || isnan(std_mult)) return -1;
+
+ // Two-pass initial window for numerical stability
+ {
+ fp_t mean = 0;
+ for( uint_t j = 0; j < k; ++j )
+ mean += x[0][j];
+ mean /= (fp_t)k;
+ fp_t variance = 0;
+ for( uint_t j = 0; j < k; ++j )
+ {
+ fp_t d = x[0][j] - mean;
+ variance += d * d;
+ }
+ variance /= (fp_t)k;
+ fp_t stddev = sqrt( variance );
+ y[1][k - 1] = mean + std_mult * stddev;
+ y[0][k - 1] = mean;
+ y[2][k - 1] = mean - std_mult * stddev;
+ }
+
+ // Sliding window: maintain running mean, two-pass variance
+ fp_t sum = 0;
+ for( uint_t j = 0; j < k; ++j )
+ sum += x[0][j];
+ fp_t rk = 1.0 / (fp_t)k;
+ for( uint_t i = k; i < n; ++i )
+ {
+ sum = sum + x[0][i] - x[0][i - k];
+ fp_t mean = sum * rk;
+ // Two-pass variance for stability
+ fp_t variance = 0;
+ for( uint_t j = i - k + 1; j <= i; ++j )
+ {
+ fp_t d = x[0][j] - mean;
+ variance += d * d;
+ }
+ variance *= rk;
+ fp_t stddev = sqrt( variance );
+ y[1][i] = mean + std_mult * stddev;
+ y[0][i] = mean;
+ y[2][i] = mean - std_mult * stddev;
+ }
+ return 0;
+}
+
diff --git a/lib/bollinger_lower.c b/lib/bollinger_lower.c
new file mode 100644
index 0000000..05a0883
--- /dev/null
+++ b/lib/bollinger_lower.c
@@ -0,0 +1,60 @@
+// Bollinger Lower Band
+//
+// bollinger_lower - BB lower
+//
+// Lower band: SMA - mult * stddev
+//
+// Inputs: 1
+// Outputs: 1 (Lower Band)
+// Constants: 2 (period, std multiplier)
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+ fp_t std_mult;
+} ts_bollinger_lower_params_t;
+
+ts_parameter_t const ts_bollinger_lower_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 2, .params_size = sizeof(ts_bollinger_lower_params_t) };
+
+
+uint_t ts_bollinger_lower_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_bollinger_lower_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_bollinger_lower(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_bollinger_lower_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_bollinger_lower_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+ fp_t std_mult = ((ts_bollinger_lower_params_t *)c)->std_mult;
+ if (isnan(std_mult) || isinf(std_mult)) return -1;
+
+ for( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t sum = 0, sum2 = 0;
+ for( uint_t j = 0; j < k; ++j )
+ {
+ fp_t v = x[0][i - k + 1 + j];
+ sum += v;
+ sum2 += v * v;
+ }
+ fp_t mean = sum / k;
+ fp_t variance = sum2 / k - mean * mean;
+ fp_t stddev = variance >= 0 ? sqrt( variance ) : 0;
+ y[0][i] = mean - std_mult * stddev;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/bollinger_pctb.c b/lib/bollinger_pctb.c
new file mode 100644
index 0000000..a3425cd
--- /dev/null
+++ b/lib/bollinger_pctb.c
@@ -0,0 +1,63 @@
+// Bollinger %b
+//
+// bollinger_pctb - %b
+//
+// Position of price relative to bands: (Price - Lower) / (Upper - Lower)
+//
+// Inputs: 1
+// Outputs: 1 (%b)
+// Constants: 2 (period, std multiplier)
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+ fp_t std_mult;
+} ts_bollinger_pctb_params_t;
+
+ts_parameter_t const ts_bollinger_pctb_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 2, .params_size = sizeof(ts_bollinger_pctb_params_t) };
+
+
+uint_t ts_bollinger_pctb_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_bollinger_pctb_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_bollinger_pctb(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_bollinger_pctb_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_bollinger_pctb_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+ fp_t std_mult = ((ts_bollinger_pctb_params_t *)c)->std_mult;
+ if (isnan(std_mult) || isinf(std_mult)) return -1;
+
+ for( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t sum = 0, sum2 = 0;
+ for( uint_t j = 0; j < k; ++j )
+ {
+ fp_t v = x[0][i - k + 1 + j];
+ sum += v;
+ sum2 += v * v;
+ }
+ fp_t mean = sum / k;
+ fp_t variance = sum2 / k - mean * mean;
+ fp_t stddev = variance >= 0 ? sqrt( variance ) : 0;
+ fp_t upper = mean + std_mult * stddev;
+ fp_t lower = mean - std_mult * stddev;
+ fp_t width = upper - lower;
+ y[0][i] = width != 0 ? ( x[0][i] - lower ) / width : 0.5;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/bollinger_upper.c b/lib/bollinger_upper.c
new file mode 100644
index 0000000..9294cfb
--- /dev/null
+++ b/lib/bollinger_upper.c
@@ -0,0 +1,60 @@
+// Bollinger Upper Band
+//
+// bollinger_upper - BB upper
+//
+// Upper band: SMA + mult * stddev
+//
+// Inputs: 1
+// Outputs: 1 (Upper Band)
+// Constants: 2 (period, std multiplier)
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+ fp_t std_mult;
+} ts_bollinger_upper_params_t;
+
+ts_parameter_t const ts_bollinger_upper_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 2, .params_size = sizeof(ts_bollinger_upper_params_t) };
+
+
+uint_t ts_bollinger_upper_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_bollinger_upper_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_bollinger_upper(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_bollinger_upper_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_bollinger_upper_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+ fp_t std_mult = ((ts_bollinger_upper_params_t *)c)->std_mult;
+ if (isnan(std_mult) || isinf(std_mult)) return -1;
+
+ for( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t sum = 0, sum2 = 0;
+ for( uint_t j = 0; j < k; ++j )
+ {
+ fp_t v = x[0][i - k + 1 + j];
+ sum += v;
+ sum2 += v * v;
+ }
+ fp_t mean = sum / k;
+ fp_t variance = sum2 / k - mean * mean;
+ fp_t stddev = variance >= 0 ? sqrt( variance ) : 0;
+ y[0][i] = mean + std_mult * stddev;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/bollinger_width.c b/lib/bollinger_width.c
new file mode 100644
index 0000000..d9bca25
--- /dev/null
+++ b/lib/bollinger_width.c
@@ -0,0 +1,62 @@
+// Bollinger Width
+//
+// bollinger_width - BB width
+//
+// Normalized band width: (Upper - Lower) / SMA * 100
+//
+// Inputs: 1
+// Outputs: 1 (Width %)
+// Constants: 2 (period, std multiplier)
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+ fp_t std_mult;
+} ts_bollinger_width_params_t;
+
+ts_parameter_t const ts_bollinger_width_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 2, .params_size = sizeof(ts_bollinger_width_params_t) };
+
+
+uint_t ts_bollinger_width_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_bollinger_width_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_bollinger_width(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_bollinger_width_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_bollinger_width_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+ fp_t std_mult = ((ts_bollinger_width_params_t *)c)->std_mult;
+ if (isnan(std_mult) || isinf(std_mult)) return -1;
+
+ for( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t sum = 0, sum2 = 0;
+ for( uint_t j = 0; j < k; ++j )
+ {
+ fp_t v = x[0][i - k + 1 + j];
+ sum += v;
+ sum2 += v * v;
+ }
+ fp_t mean = sum / k;
+ fp_t variance = sum2 / k - mean * mean;
+ fp_t stddev = variance >= 0 ? sqrt( variance ) : 0;
+ fp_t upper = mean + std_mult * stddev;
+ fp_t lower = mean - std_mult * stddev;
+ y[0][i] = mean != 0 ? ( upper - lower ) / mean * 100.0 : 0;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/candlestick_formations.c b/lib/candlestick_formations.c
new file mode 100644
index 0000000..835e5d5
--- /dev/null
+++ b/lib/candlestick_formations.c
@@ -0,0 +1,58 @@
+// Candlestick Formations
+//
+// candlestick_formations - candle
+//
+// Detects doji, engulfing, shooting star, and hammer patterns
+//
+// Inputs: 4 (OHLC)
+// Outputs: 1 (formation flag)
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_candlestick_formations_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_candlestick_formations_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 1 );
+}
+
+int ts_candlestick_formations(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_candlestick_formations_p );
+ (void)c;
+ if (n > 0) y[0][0] = 0;
+ for( uint_t i = 1; i < n; ++i )
+ {
+ fp_t const body = ts_c( x, i ) - ts_o( x, i );
+ fp_t const prev_body = ts_c( x, i - 1 ) - ts_o( x, i - 1 );
+ fp_t const range = ts_h( x, i ) - ts_l( x, i );
+ fp_t const body_abs = fabs( body );
+ fp_t const upper_wick = ts_h( x, i ) - fmax( ts_o( x, i ), ts_c( x, i ) );
+ fp_t const lower_wick = fmin( ts_o( x, i ), ts_c( x, i ) ) - ts_l( x, i );
+
+ y[0][i] = 0;
+
+ if( range > 0 && body_abs / range < 0.1 )
+ y[0][i] = 1; // doji
+ else if( prev_body > 0 && body < 0 && body_abs >= fabs( prev_body ) )
+ y[0][i] = 2; // bearish engulfing
+ else if( prev_body < 0 && body > 0 && body_abs >= fabs( prev_body ) )
+ y[0][i] = 3; // bullish engulfing
+ else if( upper_wick >= 2 * body_abs && body <= 0 )
+ y[0][i] = 4; // shooting star
+ else if( lower_wick >= 2 * body_abs && body >= 0 )
+ y[0][i] = 5; // hammer
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/catalan.c b/lib/catalan.c
new file mode 100644
index 0000000..4a6ed9e
--- /dev/null
+++ b/lib/catalan.c
@@ -0,0 +1,54 @@
+// Catalan Number
+//
+// catalan - C(n)
+//
+// nth Catalan number: C(n) = (2n)! / ((n+1)! * n!)
+//
+// Inputs: 1 (n)
+// Outputs: 1 (Catalan number)
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_catalan_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_catalan_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_catalan(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_catalan_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xv = x[0][i];
+ if (isnan(xv) || isinf(xv) || xv < 0 || xv != (fp_t)(uint_t)xv) {
+ y[0][i] = 0;
+ continue;
+ }
+ uint_t n_val = (uint_t)xv;
+ fp_t result = 1.0;
+ for( uint_t j = 0; j < n_val; ++j )
+ {
+ result *= (fp_t)(j + n_val + 1);
+ result /= (fp_t)(j + 1);
+ }
+ result /= (fp_t)(n_val + 1);
+ y[0][i] = result;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/cauchy_cdf.c b/lib/cauchy_cdf.c
new file mode 100644
index 0000000..dadaaf3
--- /dev/null
+++ b/lib/cauchy_cdf.c
@@ -0,0 +1,40 @@
+// Cauchy CDF
+//
+// cauchy_cdf - Cauchy cumulative distribution
+//
+// F(x) = 0.5 + atan(x) / PI
+//
+// Inputs: 1
+// Outputs: 1 (CDF value)
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_cauchy_cdf_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_cauchy_cdf_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_cauchy_cdf(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_cauchy_cdf_p );
+ (void)c;
+ /* CDF = 0.5 + atan(x) / PI */
+ for (uint_t i = 0; i < n; ++i)
+ {
+ y[0][i] = 0.5 + atan(x[0][i]) / PI;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/cauchy_pdf.c b/lib/cauchy_pdf.c
new file mode 100644
index 0000000..0d427c1
--- /dev/null
+++ b/lib/cauchy_pdf.c
@@ -0,0 +1,39 @@
+// Cauchy PDF
+//
+// cauchy_pdf - Cauchy probability density
+//
+// f(x) = 1 / (PI * (1 + x^2))
+//
+// Inputs: 1
+// Outputs: 1 (PDF value)
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_cauchy_pdf_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_cauchy_pdf_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_cauchy_pdf(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_cauchy_pdf_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = 1.0 / ( PI * ( 1.0 + x[0][i] * x[0][i] ) );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/cbrt.c b/lib/cbrt.c
new file mode 100644
index 0000000..2de15d2
--- /dev/null
+++ b/lib/cbrt.c
@@ -0,0 +1,41 @@
+// Cube Root
+//
+// cbrt - cube root
+//
+// y[t] = ∛(x[t])
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_cbrt_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_cbrt_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_cbrt(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_cbrt_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = cbrt( x[0][i] );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/cci.c b/lib/cci.c
new file mode 100644
index 0000000..956f619
--- /dev/null
+++ b/lib/cci.c
@@ -0,0 +1,89 @@
+// Commodity Channel Index
+//
+// cci - CCI
+//
+// (TP - SMA(TP)) / (0.015 * MAD(TP)), where TP = (H+L+C)/3
+//
+// Inputs: 4 (OHLC)
+// Outputs: 1 (CCI)
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_cci_params_t;
+
+ts_parameter_t const ts_cci_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_cci_params_t) };
+
+uint_t ts_cci_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_cci_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_cci(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_cci_p );
+ if ( !c ) return -1;
+ TS_CHECK_INPUT(x, x_n, n);
+ uint_t k = ((ts_cci_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ // Compute initial typical prices
+ fp_t tp_sum = 0;
+ for( uint_t j = 0; j < k; ++j )
+ {
+ tp_sum += ( ts_h( x, j ) + ts_l( x, j ) + ts_c( x, j ) ) / 3.0;
+ }
+ fp_t tp_sma = tp_sum / (fp_t)k;
+
+ // Compute initial MAD
+ fp_t mad_sum = 0;
+ for( uint_t j = 0; j < k; ++j )
+ {
+ fp_t tp = ( ts_h( x, j ) + ts_l( x, j ) + ts_c( x, j ) ) / 3.0;
+ mad_sum += fabs( tp - tp_sma );
+ }
+ fp_t mad = mad_sum / (fp_t)k;
+
+ {
+ fp_t tp = ( ts_h( x, k - 1 ) + ts_l( x, k - 1 ) + ts_c( x, k - 1 ) ) / 3.0;
+ if( mad == 0 )
+ y[0][k - 1] = 0;
+ else
+ y[0][k - 1] = ( tp - tp_sma ) / ( 0.015 * mad );
+ }
+
+ // Sliding window: incremental tp_sum, but MAD needs recompute when tp_sma changes
+ // MAD depends on tp_sma which changes each step, so we can't fully incremental MAD
+ // However, we can still use incremental tp_sum
+ for( uint_t i = k; i < n; ++i )
+ {
+ fp_t tp_new = ( ts_h( x, i ) + ts_l( x, i ) + ts_c( x, i ) ) / 3.0;
+ fp_t tp_old = ( ts_h( x, i - k ) + ts_l( x, i - k ) + ts_c( x, i - k ) ) / 3.0;
+ tp_sum = tp_sum + tp_new - tp_old;
+ tp_sma = tp_sum / (fp_t)k;
+
+ mad_sum = 0;
+ for( uint_t j = 0; j < k; ++j )
+ {
+ fp_t tp = ( ts_h( x, i - k + 1 + j ) + ts_l( x, i - k + 1 + j ) + ts_c( x, i - k + 1 + j ) ) / 3.0;
+ mad_sum += fabs( tp - tp_sma );
+ }
+ mad = mad_sum / (fp_t)k;
+
+ if( mad == 0 )
+ y[0][i] = 0;
+ else
+ y[0][i] = ( tp_new - tp_sma ) / ( 0.015 * mad );
+ }
+
+ return 0;
+}
+
diff --git a/lib/ceiling.c b/lib/ceiling.c
new file mode 100644
index 0000000..3dc53d8
--- /dev/null
+++ b/lib/ceiling.c
@@ -0,0 +1,42 @@
+// Ceiling
+//
+// ceiling - ceil
+//
+// f( x ) = ⌈ x ⌉
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_ceiling_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_ceiling_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_ceiling(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_ceiling_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = ceil( x[0][i] );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/center_of_gravity.c b/lib/center_of_gravity.c
new file mode 100644
index 0000000..3186c44
--- /dev/null
+++ b/lib/center_of_gravity.c
@@ -0,0 +1,56 @@
+// Center of Gravity
+//
+// center_of_gravity - COG
+//
+// 1 - 2 * SUM(i*x_i) / ((k+1) * SUM(x_i)), oscillates around 0
+//
+// Inputs: 1
+// Outputs: 1 (COG)
+// Constants: 1 (period)
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_center_of_gravity_params_t;
+
+ts_parameter_t const ts_center_of_gravity_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_center_of_gravity_params_t) };
+
+uint_t ts_center_of_gravity_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_center_of_gravity_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_center_of_gravity(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_center_of_gravity_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_center_of_gravity_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t sum_ix = 0;
+ fp_t sum_x = 0;
+ for( uint_t j = 0; j < k; ++j )
+ {
+ fp_t idx = (fp_t)(j + 1);
+ fp_t val = x[0][i - k + 1 + j];
+ sum_ix += idx * val;
+ sum_x += val;
+ }
+ if( sum_x == 0 )
+ y[0][i] = 0;
+ else
+ y[0][i] = 1.0 - 2.0 * sum_ix / ( ( (fp_t)k + 1.0 ) * sum_x );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/chande_forecast_oscillator.c b/lib/chande_forecast_oscillator.c
new file mode 100644
index 0000000..6e65bbe
--- /dev/null
+++ b/lib/chande_forecast_oscillator.c
@@ -0,0 +1,67 @@
+// Chande Forecast Oscillator
+//
+// chande_forecast_oscillator - CFO
+//
+// (Actual - Forecast) / Forecast * 100, using linear regression forecast
+//
+// Inputs: 1
+// Outputs: 1 (Oscillator %)
+// Constants: 1 (period)
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_chande_forecast_oscillator_params_t;
+
+ts_parameter_t const ts_chande_forecast_oscillator_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_chande_forecast_oscillator_params_t) };
+
+uint_t ts_chande_forecast_oscillator_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_chande_forecast_oscillator_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_chande_forecast_oscillator(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_chande_forecast_oscillator_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_chande_forecast_oscillator_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t sum_x = 0, sum_y = 0, sum_xy = 0, sum_x2 = 0;
+ for( uint_t j = 0; j < k; ++j )
+ {
+ fp_t xv = (fp_t)j;
+ fp_t yv = x[0][i - k + 1 + j];
+ sum_x += xv;
+ sum_y += yv;
+ sum_xy += xv * yv;
+ sum_x2 += xv * xv;
+ }
+ fp_t denom = (fp_t)k * sum_x2 - sum_x * sum_x;
+ fp_t forecast;
+ if( denom == 0 )
+ forecast = sum_y / (fp_t)k;
+ else
+ {
+ fp_t slope = ( (fp_t)k * sum_xy - sum_x * sum_y ) / denom;
+ fp_t intercept = ( sum_y - slope * sum_x ) / (fp_t)k;
+ forecast = intercept + slope * (fp_t)k;
+ }
+ if( forecast != 0 )
+ y[0][i] = ( x[0][i] - forecast ) / forecast * 100.0;
+ else
+ y[0][i] = 0;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/change.c b/lib/change.c
new file mode 100644
index 0000000..84815a5
--- /dev/null
+++ b/lib/change.c
@@ -0,0 +1,47 @@
+// Percentage Change
+//
+// change - pct_change
+//
+// (x[t] - x[t-k]) / x[t-k] * 100
+//
+// Inputs: 1
+// Outputs: 1 (Change %)
+// Constants: 1 (period)
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_change_params_t;
+
+ts_parameter_t const ts_change_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_change_params_t) };
+
+uint_t ts_change_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_change_params_t *)c)->k : 0 );
+}
+
+int ts_change(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_change_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_change_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for( uint_t i = k; i < n; ++i )
+ {
+ if( x[0][i - k] == 0 ) { y[0][i] = 0; continue; }
+ y[0][i] = ( x[0][i] - x[0][i - k] ) / x[0][i - k] * 100;
+ }
+
+ return 0;
+}
+
+
+
diff --git a/lib/channel.c b/lib/channel.c
new file mode 100644
index 0000000..a2d8bd9
--- /dev/null
+++ b/lib/channel.c
@@ -0,0 +1,64 @@
+// Channel
+//
+// channel - Donchian-style channel
+//
+// Upper: rolling highest high, Lower: rolling lowest low over period k
+//
+// Inputs: 4 (OHLC)
+// Outputs: 2 (Upper, Lower)
+// Constants: 1 (period)
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_channel_params_t;
+
+ts_parameter_t const ts_channel_p = { .n_inputs = 4, .n_outputs = 2, .n_constants = 1, .params_size = sizeof(ts_channel_params_t) };
+
+uint_t ts_channel_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_channel_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_channel(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_channel_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_channel_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ uint_t xi = k - 1;
+ uint_t xj = 0;
+ uint_t yi = k - 1;
+
+ for( ; xi < n; xi++, xj++, yi++ )
+ {
+ y[0][yi] = x[1][xj];
+ for( uint_t i = xj; i <= xi; i++ )
+ {
+ if( x[1][i] > y[0][yi] )
+ {
+ y[0][yi] = x[1][i];
+ }
+ }
+
+ y[1][yi] = x[2][xj];
+ for( uint_t i = xj; i <= xi; i++ )
+ {
+ if( x[2][i] < y[1][yi] )
+ {
+ y[1][yi] = x[2][i];
+ }
+ }
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/chebyshev_distance.c b/lib/chebyshev_distance.c
new file mode 100644
index 0000000..58c055d
--- /dev/null
+++ b/lib/chebyshev_distance.c
@@ -0,0 +1,39 @@
+// Chebyshev Distance
+//
+// chebyshev_distance - L∞ norm
+//
+// max(|x|, |y|)
+//
+// Inputs: 2
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_chebyshev_distance_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_chebyshev_distance_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_chebyshev_distance(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_chebyshev_distance_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = fmax( fabs( x[0][i] ), fabs( x[1][i] ) );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/chebyshev_t.c b/lib/chebyshev_t.c
new file mode 100644
index 0000000..a0230b8
--- /dev/null
+++ b/lib/chebyshev_t.c
@@ -0,0 +1,61 @@
+// Chebyshev Polynomial of the First Kind
+//
+// chebyshev_t - T_n(x)
+//
+// T_0 = 1, T_1 = x, T_n = 2x*T_(n-1) - T_(n-2)
+//
+// Inputs: 2 (x, n)
+// Outputs: 1 (T_n(x))
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_chebyshev_t_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_chebyshev_t_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_chebyshev_t(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_chebyshev_t_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xv = x[0][i];
+ uint_t n_val = (uint_t)x[1][i];
+ if (x[1][i] < 0 || x[1][i] > 10000) { y[0][i] = 0; continue; }
+ if( n_val == 0 )
+ {
+ y[0][i] = 1.0;
+ }
+ else if( n_val == 1 )
+ {
+ y[0][i] = xv;
+ }
+ else
+ {
+ fp_t t0 = 1.0;
+ fp_t t1 = xv;
+ for( uint_t k = 2; k <= n_val; ++k )
+ {
+ fp_t t2 = 2.0 * xv * t1 - t0;
+ t0 = t1;
+ t1 = t2;
+ }
+ y[0][i] = t1;
+ }
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/chi_squared_pdf.c b/lib/chi_squared_pdf.c
new file mode 100644
index 0000000..7f2ba09
--- /dev/null
+++ b/lib/chi_squared_pdf.c
@@ -0,0 +1,49 @@
+// Chi-Squared PDF
+//
+// chi_squared_pdf - χ² probability density
+//
+// f(x; k) = x^(k/2-1) * e^(-x/2) / (2^(k/2) * Γ(k/2)), for x > 0
+//
+// Inputs: 1
+// Outputs: 1 (PDF value)
+// Constants: 1 (degrees of freedom)
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ fp_t k;
+} ts_chi_squared_pdf_params_t;
+
+ts_parameter_t const ts_chi_squared_pdf_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_chi_squared_pdf_params_t) };
+
+uint_t ts_chi_squared_pdf_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_chi_squared_pdf(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_chi_squared_pdf_p );
+ if ( !c ) return -1;
+ fp_t k = ((ts_chi_squared_pdf_params_t *)c)->k;
+ if ( k <= 0 )
+ {
+ for( uint_t i = 0; i < n; ++i ) y[0][i] = 0;
+ return -1;
+ }
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xi = x[0][i];
+ y[0][i] = xi > 0 ? pow( xi, k / 2.0 - 1.0 ) * exp( -xi / 2.0 ) / ( pow( 2.0, k / 2.0 ) * tgamma( k / 2.0 ) ) : 0.0;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/choppiness_index.c b/lib/choppiness_index.c
new file mode 100644
index 0000000..55ecb46
--- /dev/null
+++ b/lib/choppiness_index.c
@@ -0,0 +1,68 @@
+// Choppiness Index
+//
+// choppiness_index - CHOP
+//
+// 100 * log(SUM(H-L) / Range) / log(k), measures trend vs chop
+//
+// Inputs: 3 (HLC)
+// Outputs: 1 (CHOP)
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_choppiness_index_params_t;
+
+ts_parameter_t const ts_choppiness_index_p = { .n_inputs = 3, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_choppiness_index_params_t) };
+
+uint_t ts_choppiness_index_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? 2 * ((ts_choppiness_index_params_t *)c)->k : 0 );
+}
+
+int ts_choppiness_index(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_choppiness_index_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_choppiness_index_params_t *)c)->k;
+ uint_t range_period = 2 * k;
+ if (k == 0 || n < range_period) return -1;
+
+ for ( uint_t i = range_period; i < n; ++i )
+ {
+ fp_t sum_range = 0;
+ for ( uint_t j = i - k + 1; j <= i; ++j )
+ {
+ sum_range += fabs( ts_h( x, j ) - ts_l( x, j ) );
+ }
+
+ fp_t hh = ts_h( x, i - range_period + 1 );
+ fp_t ll = ts_l( x, i - range_period + 1 );
+ for ( uint_t j = i - range_period + 1; j <= i; ++j )
+ {
+ if ( ts_h( x, j ) > hh ) hh = ts_h( x, j );
+ if ( ts_l( x, j ) < ll ) ll = ts_l( x, j );
+ }
+
+ fp_t range = hh - ll;
+ if ( range == 0 || sum_range == 0 || k <= 1 )
+ {
+ y[0][i] = 0;
+ }
+ else
+ {
+ y[0][i] = 100.0 * log( sum_range / range ) / log( ( fp_t )k );
+ }
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/close_minus_open.c b/lib/close_minus_open.c
new file mode 100644
index 0000000..45932cb
--- /dev/null
+++ b/lib/close_minus_open.c
@@ -0,0 +1,39 @@
+// Close Minus Open
+//
+// close_minus_open - C - O
+//
+// Difference between close and open price
+//
+// Inputs: 5 (OHLCV)
+// Outputs: 1 (C - O)
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_close_minus_open_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_close_minus_open_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_close_minus_open(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_close_minus_open_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = x[3][i] - x[0][i];
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/close_to_high.c b/lib/close_to_high.c
new file mode 100644
index 0000000..dc7cf7b
--- /dev/null
+++ b/lib/close_to_high.c
@@ -0,0 +1,40 @@
+// Close to High Ratio
+//
+// close_to_high - CT_H
+//
+// (H - C) / (H - L), position of close within daily range
+//
+// Inputs: 5 (OHLCV)
+// Outputs: 1 (ratio)
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_close_to_high_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_close_to_high_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_close_to_high(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_close_to_high_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t denom = x[1][i] - x[2][i];
+ y[0][i] = denom != 0 ? (x[1][i] - x[3][i]) / denom : 0;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/close_to_open_ratio.c b/lib/close_to_open_ratio.c
new file mode 100644
index 0000000..d857dd2
--- /dev/null
+++ b/lib/close_to_open_ratio.c
@@ -0,0 +1,39 @@
+// Close to Open Ratio
+//
+// close_to_open_ratio - C/O
+//
+// Ratio of close to open price
+//
+// Inputs: 5 (OHLCV)
+// Outputs: 1 (ratio)
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_close_to_open_ratio_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_close_to_open_ratio_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_close_to_open_ratio(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_close_to_open_ratio_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = x[0][i] != 0 ? x[3][i] / x[0][i] : 0;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/cmf.c b/lib/cmf.c
new file mode 100644
index 0000000..7ac0402
--- /dev/null
+++ b/lib/cmf.c
@@ -0,0 +1,67 @@
+// Chaikin Money Flow
+//
+// cmf - CMF
+//
+// SUM(MF * V) / SUM(V) over period, where MF = ((C-L)-(H-C))/(H-L)
+//
+// Inputs: 5 (OHLCV)
+// Outputs: 1 (CMF)
+// Constants: 1 (period)
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_cmf_params_t;
+
+ts_parameter_t const ts_cmf_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_cmf_params_t) };
+
+uint_t ts_cmf_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_cmf_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_cmf(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_cmf_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_cmf_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ fp_t sum_mf = 0;
+ fp_t sum_vol = 0;
+
+ for ( uint_t i = 0; i < k; ++i )
+ {
+ fp_t hl = ts_h( x, i ) - ts_l( x, i );
+ fp_t mf = hl != 0 ? ( ( ts_c( x, i ) - ts_l( x, i ) ) - ( ts_h( x, i ) - ts_c( x, i ) ) ) / hl * ts_v( x, i ) : 0;
+ sum_mf += mf;
+ sum_vol += ts_v( x, i );
+ }
+
+ y[0][k - 1] = sum_vol != 0 ? sum_mf / sum_vol : 0;
+
+ for ( uint_t i = k; i < n; ++i )
+ {
+ fp_t hl_prev = ts_h( x, i - k ) - ts_l( x, i - k );
+ fp_t mf_prev = hl_prev != 0 ? ( ( ts_c( x, i - k ) - ts_l( x, i - k ) ) - ( ts_h( x, i - k ) - ts_c( x, i - k ) ) ) / hl_prev * ts_v( x, i - k ) : 0;
+
+ fp_t hl_cur = ts_h( x, i ) - ts_l( x, i );
+ fp_t mf_cur = hl_cur != 0 ? ( ( ts_c( x, i ) - ts_l( x, i ) ) - ( ts_h( x, i ) - ts_c( x, i ) ) ) / hl_cur * ts_v( x, i ) : 0;
+
+ sum_mf = sum_mf - mf_prev + mf_cur;
+ sum_vol = sum_vol - ts_v( x, i - k ) + ts_v( x, i );
+
+ y[0][i] = sum_vol != 0 ? sum_mf / sum_vol : 0;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/cmo.c b/lib/cmo.c
new file mode 100644
index 0000000..9db5622
--- /dev/null
+++ b/lib/cmo.c
@@ -0,0 +1,72 @@
+// Chande Momentum Oscillator
+//
+// cmo - CMO
+//
+// 100 * (SUM(up) - SUM(down)) / (SUM(up) + SUM(down))
+//
+// Inputs: 1
+// Outputs: 1 (CMO)
+// Constants: 1 (period)
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_cmo_params_t;
+
+ts_parameter_t const ts_cmo_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_cmo_params_t) };
+
+uint_t ts_cmo_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_cmo_params_t *)c)->k : 0 );
+}
+
+int ts_cmo(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_cmo_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_cmo_params_t *)c)->k;
+ if (k == 0 || n <= k) return -1;
+
+ for (uint_t j = 0; j < k; ++j) y[0][j] = 0;
+
+ fp_t up_sum = 0;
+ fp_t down_sum = 0;
+
+ for( uint_t i = 1; i <= k; ++i )
+ {
+ fp_t diff = x[0][i] - x[0][i - 1];
+ if( diff > 0 )
+ up_sum += diff;
+ else if( diff < 0 )
+ down_sum += (-diff);
+ }
+
+ y[0][k] = (up_sum + down_sum) == 0 ? 0 : 100.0 * (up_sum - down_sum) / (up_sum + down_sum);
+
+ for( uint_t i = k + 1; i < n; ++i )
+ {
+ fp_t diff_new = x[0][i] - x[0][i - 1];
+ fp_t diff_old = x[0][i - k] - x[0][i - k - 1];
+
+ fp_t up_new = diff_new > 0 ? diff_new : 0;
+ fp_t down_new = diff_new < 0 ? (-diff_new) : 0;
+ fp_t up_old = diff_old > 0 ? diff_old : 0;
+ fp_t down_old = diff_old < 0 ? (-diff_old) : 0;
+
+ up_sum = up_sum - up_old + up_new;
+ down_sum = down_sum - down_old + down_new;
+
+ y[0][i] = (up_sum + down_sum) == 0 ? 0 : 100.0 * (up_sum - down_sum) / (up_sum + down_sum);
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/coppock_curve.c b/lib/coppock_curve.c
new file mode 100644
index 0000000..dfa1ad2
--- /dev/null
+++ b/lib/coppock_curve.c
@@ -0,0 +1,75 @@
+// Coppock Curve
+//
+// coppock_curve - WMS
+//
+// WMA14(ROC14 + ROC11), weighted moving average of combined ROCs
+//
+// Inputs: 1
+// Outputs: 1 (Coppock)
+// Constants: 0
+
+#include <math.h>
+#include <stdlib.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_coppock_curve_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_coppock_curve_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 14 );
+}
+
+int ts_coppock_curve(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_coppock_curve_p );
+ (void)c;
+ uint_t roc_long = 14;
+ uint_t roc_short = 11;
+ uint_t wma_period = 10;
+
+ if (n == 0 || n < roc_long) return -1;
+
+ fp_t *combined = calloc( n, sizeof( fp_t ) );
+ if ( !combined ) return -1;
+
+ for ( uint_t i = roc_long; i < n; ++i )
+ {
+ if ( x[0][i - roc_long] == 0 || x[0][i - roc_short] == 0 )
+ {
+ combined[i - roc_long] = 0;
+ continue;
+ }
+ fp_t roc14 = ( x[0][i] - x[0][i - roc_long] ) / x[0][i - roc_long] * 100.0;
+ fp_t roc11 = ( x[0][i] - x[0][i - roc_short] ) / x[0][i - roc_short] * 100.0;
+ combined[i - roc_long] = roc14 + roc11;
+ }
+
+ for (uint_t j = 0; j < n && j < roc_long; ++j) y[0][j] = 0;
+
+ for ( uint_t i = roc_long; i < n; ++i )
+ {
+ uint_t ci = i - roc_long;
+ fp_t wsum = 0;
+ fp_t wtotal = 0;
+ uint_t start = ci + 1 >= wma_period ? ci + 1 - wma_period : 0;
+ for ( uint_t j = start; j <= ci; ++j )
+ {
+ uint_t weight = ci - j + 1;
+ wsum += combined[j] * (fp_t)weight;
+ wtotal += (fp_t)weight;
+ }
+ y[0][ci + roc_long] = wtotal != 0 ? wsum / wtotal : 0;
+ }
+
+ free( combined );
+ return 0;
+}
+
+
+
diff --git a/lib/copysign.c b/lib/copysign.c
new file mode 100644
index 0000000..f146741
--- /dev/null
+++ b/lib/copysign.c
@@ -0,0 +1,39 @@
+// Copy Sign
+//
+// copysign - copy sign
+//
+// Returns x[0] with the sign of x[1]
+//
+// Inputs: 2
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_copysign_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_copysign_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_copysign(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_copysign_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = copysign( x[0][i], x[1][i] );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/correlation_coefficient.c b/lib/correlation_coefficient.c
new file mode 100644
index 0000000..d20d1ea
--- /dev/null
+++ b/lib/correlation_coefficient.c
@@ -0,0 +1,75 @@
+// Correlation Coefficient
+//
+// correlation_coefficient - corr
+//
+// Pearson r = (n*Σxy - Σx*Σy) / √((n*Σx²-Σx²)(n*Σy²-Σy²))
+//
+// Inputs: 2
+// Outputs: 1 (r)
+// Constants: 1 (period)
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_correlation_coefficient_params_t;
+
+ts_parameter_t const ts_correlation_coefficient_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_correlation_coefficient_params_t) };
+
+uint_t ts_correlation_coefficient_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_correlation_coefficient_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_correlation_coefficient(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_correlation_coefficient_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_correlation_coefficient_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ fp_t sum_x = 0, sum_y = 0, sum_xy = 0, sum_x2 = 0, sum_y2 = 0;
+
+ for ( uint_t i = 0; i < k; ++i )
+ {
+ fp_t xi = x[0][i];
+ fp_t yi = x[1][i];
+ sum_x += xi;
+ sum_y += yi;
+ sum_xy += xi * yi;
+ sum_x2 += xi * xi;
+ sum_y2 += yi * yi;
+ }
+
+ fp_t denom = ( k * sum_x2 - sum_x * sum_x ) * ( k * sum_y2 - sum_y * sum_y );
+ fp_t numer = k * sum_xy - sum_x * sum_y;
+ y[0][k - 1] = denom > 0 ? numer / sqrt( denom ) : 0;
+
+ for ( uint_t i = k; i < n; ++i )
+ {
+ fp_t xi_old = x[0][i - k];
+ fp_t yi_old = x[1][i - k];
+ fp_t xi_new = x[0][i];
+ fp_t yi_new = x[1][i];
+
+ sum_x = sum_x - xi_old + xi_new;
+ sum_y = sum_y - yi_old + yi_new;
+ sum_xy = sum_xy - xi_old * yi_old + xi_new * yi_new;
+ sum_x2 = sum_x2 - xi_old * xi_old + xi_new * xi_new;
+ sum_y2 = sum_y2 - yi_old * yi_old + yi_new * yi_new;
+
+ denom = ( k * sum_x2 - sum_x * sum_x ) * ( k * sum_y2 - sum_y * sum_y );
+ numer = k * sum_xy - sum_x * sum_y;
+ y[0][i] = denom > 0 ? numer / sqrt( denom ) : 0;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/cosecant.c b/lib/cosecant.c
new file mode 100644
index 0000000..e2aea17
--- /dev/null
+++ b/lib/cosecant.c
@@ -0,0 +1,43 @@
+// Cosecant
+//
+// cosecant - csc
+//
+// f( x ) = csc( x ) = 1 / sin( x )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_cosecant_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_cosecant_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_cosecant(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_cosecant_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t s = sin( x[0][i] );
+ y[0][i] = ( fabs( s ) > 1e-15 ) ? _ts_reciprocal( s ) : ( s > 0 ? (fp_t)INFINITY : (fp_t)-INFINITY );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/cosecant_hyperbolic.c b/lib/cosecant_hyperbolic.c
new file mode 100644
index 0000000..96898fc
--- /dev/null
+++ b/lib/cosecant_hyperbolic.c
@@ -0,0 +1,45 @@
+// Hyperbolic Cosecant
+//
+// cosecant_hyperbolic - csch
+//
+// f( x ) = csch( x )
+// = 1 / sinh( x )
+// = 2 / ( e^x - e^( -x ) )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_cosecant_hyperbolic_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_cosecant_hyperbolic_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_cosecant_hyperbolic(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_cosecant_hyperbolic_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t s = sinh( x[0][i] );
+ y[0][i] = ( fabs( s ) > 1e-15 ) ? _ts_reciprocal( s ) : ( s > 0 ? (fp_t)INFINITY : (fp_t)-INFINITY );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/cosecant_hyperbolic_inverse.c b/lib/cosecant_hyperbolic_inverse.c
new file mode 100644
index 0000000..a9f0984
--- /dev/null
+++ b/lib/cosecant_hyperbolic_inverse.c
@@ -0,0 +1,44 @@
+// Inverse Hyperbolic Cosecant
+//
+// cosecant_hyperbolic_inverse - acsch
+//
+// f( x ) = acsch( x )
+// = ln( ( 1 / x ) + √( ( 1 / x^2 ) + 1 ) ) x ≠ 0
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_cosecant_hyperbolic_inverse_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_cosecant_hyperbolic_inverse_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_cosecant_hyperbolic_inverse(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_cosecant_hyperbolic_inverse_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xv = x[0][i];
+ y[0][i] = xv == 0.0 ? (fp_t)NAN : asinh( _ts_reciprocal( xv ) );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/cosecant_inverse.c b/lib/cosecant_inverse.c
new file mode 100644
index 0000000..b5f0bbd
--- /dev/null
+++ b/lib/cosecant_inverse.c
@@ -0,0 +1,44 @@
+// Inverse Cosecant
+//
+// cosecant_inverse - acsc
+//
+// f( x ) = acsc( x )
+// = asin( 1 / x )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_cosecant_inverse_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_cosecant_inverse_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_cosecant_inverse(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_cosecant_inverse_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t r = _ts_reciprocal( x[0][i] );
+ y[0][i] = ( r >= -1.0 && r <= 1.0 ) ? asin( r ) : 0;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/cosine.c b/lib/cosine.c
new file mode 100644
index 0000000..ee039e7
--- /dev/null
+++ b/lib/cosine.c
@@ -0,0 +1,42 @@
+// Cosine
+//
+// cosine - cos
+//
+// f( x ) = cos( x )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_cosine_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_cosine_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_cosine(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_cosine_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = cos( x[0][i] );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/cosine_hyperbolic.c b/lib/cosine_hyperbolic.c
new file mode 100644
index 0000000..51f84a0
--- /dev/null
+++ b/lib/cosine_hyperbolic.c
@@ -0,0 +1,43 @@
+// Hyperbolic Cosine
+//
+// cosine_hyperbolic - cosh
+//
+// f( x ) = cosh( x )
+// = 0.5 * ( e^x + e^( -x ) )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_cosine_hyperbolic_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_cosine_hyperbolic_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_cosine_hyperbolic(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_cosine_hyperbolic_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = cosh( x[0][i] );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/cosine_inverse.c b/lib/cosine_inverse.c
new file mode 100644
index 0000000..dead58a
--- /dev/null
+++ b/lib/cosine_inverse.c
@@ -0,0 +1,43 @@
+// Inverse Cosine
+//
+// cosine_inverse - acos
+//
+// f( x ) = acos( x )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_cosine_inverse_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_cosine_inverse_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_cosine_inverse(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_cosine_inverse_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t v = x[0][i];
+ y[0][i] = ( v >= -1.0 && v <= 1.0 ) ? acos( v ) : 0;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/cosine_similarity.c b/lib/cosine_similarity.c
new file mode 100644
index 0000000..f93f9b7
--- /dev/null
+++ b/lib/cosine_similarity.c
@@ -0,0 +1,40 @@
+// Cosine Similarity
+//
+// cosine_similarity - cos_sim
+//
+// Sign of the product: +1 if positive, -1 if negative, 0 if zero
+//
+// Inputs: 2
+// Outputs: 1 (similarity)
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_cosine_similarity_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_cosine_similarity_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_cosine_similarity(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_cosine_similarity_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t p = x[0][i] * x[1][i];
+ y[0][i] = p > 0.0 ? 1.0 : ( p < 0.0 ? -1.0 : 0.0 );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/cotangent.c b/lib/cotangent.c
new file mode 100644
index 0000000..b23b6da
--- /dev/null
+++ b/lib/cotangent.c
@@ -0,0 +1,43 @@
+// Cotangent
+//
+// cotangent - cot
+//
+// f( x ) = cot( x ) = 1 / tan( x )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_cotangent_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_cotangent_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_cotangent(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_cotangent_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t t = tan( x[0][i] );
+ y[0][i] = ( fabs( t ) > 1e-15 ) ? _ts_reciprocal( t ) : ( t > 0 ? (fp_t)INFINITY : (fp_t)-INFINITY );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/cotangent_hyperbolic.c b/lib/cotangent_hyperbolic.c
new file mode 100644
index 0000000..dca6a4f
--- /dev/null
+++ b/lib/cotangent_hyperbolic.c
@@ -0,0 +1,46 @@
+// Hyperbolic Cotangent
+//
+// cotangent_hyperbolic - coth
+//
+// f( x ) = coth( x )
+// = 1 / tanh( x )
+// = cosh( x ) / sinh( x )
+// = ( e^2x + 1 ) / ( e^2x - 1 )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_cotangent_hyperbolic_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_cotangent_hyperbolic_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_cotangent_hyperbolic(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_cotangent_hyperbolic_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t t = tanh( x[0][i] );
+ y[0][i] = ( fabs( t ) > 1e-15 ) ? _ts_reciprocal( t ) : ( t > 0 ? (fp_t)INFINITY : (fp_t)-INFINITY );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/cotangent_hyperbolic_inverse.c b/lib/cotangent_hyperbolic_inverse.c
new file mode 100644
index 0000000..3fe2c14
--- /dev/null
+++ b/lib/cotangent_hyperbolic_inverse.c
@@ -0,0 +1,45 @@
+// Inverse Hyperbolic Cotangent
+//
+// cottangent_hyperbolic_inverse - acoth
+//
+// f( x ) = acoth( x )
+// = 0.5 * ln( ( x + 1 ) / ( x - 1 ) ) |x| > 1
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_cotangent_hyperbolic_inverse_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_cotangent_hyperbolic_inverse_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_cotangent_hyperbolic_inverse(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_cotangent_hyperbolic_inverse_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xi = x[0][i];
+ fp_t r = _ts_reciprocal( xi );
+ y[0][i] = ( r > -1.0 && r < 1.0 ) ? atanh( r ) : 0;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/cotangent_inverse.c b/lib/cotangent_inverse.c
new file mode 100644
index 0000000..6ad3318
--- /dev/null
+++ b/lib/cotangent_inverse.c
@@ -0,0 +1,46 @@
+// Inverse Cotangent
+//
+// cotangent_inverse - acot
+//
+// f( x ) = acot( x )
+// = atan( 1 / x )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_cotangent_inverse_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_cotangent_inverse_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_cotangent_inverse(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_cotangent_inverse_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t v = x[0][i];
+ if ( v > 0 ) y[0][i] = atan( 1.0 / v );
+ else if ( v < 0 ) y[0][i] = atan( 1.0 / v ) + PI;
+ else y[0][i] = PI / 2.0;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/crossover.c b/lib/crossover.c
new file mode 100644
index 0000000..92a0c9e
--- /dev/null
+++ b/lib/crossover.c
@@ -0,0 +1,60 @@
+// Crossover
+//
+// crossover - crossover
+//
+// ⎧ -1 if x0_i < x1_i and x0_i-1 > x1_i-1
+// y_i = ⎨ 0 if x = 0
+// ⎩ 1 if x0_i > x1_i and x0_i-1 < x1_i-1
+//
+// Inputs: 2
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_crossover_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_crossover_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 1 );
+}
+
+int ts_crossover(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_crossover_p );
+ (void)c;
+ if ( n < 2 ) return -1;
+
+ uint_t xi;
+ uint_t yi;
+
+ xi = 1;
+ yi = 1;
+
+ y[0][0] = 0;
+ for( ; xi < n; xi++, yi++ )
+ {
+ if( x[0][xi] > x[1][xi] && x[0][xi - 1] <= x[1][xi - 1] )
+ y[0][yi] = 1;
+
+ else if( x[0][xi] < x[1][xi] && x[0][xi - 1] >= x[1][xi - 1] )
+ y[0][yi] = -1;
+
+ else
+ y[0][yi] = 0;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/cumulative_product.c b/lib/cumulative_product.c
new file mode 100644
index 0000000..5701deb
--- /dev/null
+++ b/lib/cumulative_product.c
@@ -0,0 +1,42 @@
+// Cumulative Product
+//
+// cumulative_product - cumprod
+//
+// y[i] = x[0] * x[1] * ... * x[i]
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_cumulative_product_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_cumulative_product_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_cumulative_product(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_cumulative_product_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ if( i == 0 )
+ y[0][i] = x[0][i];
+ else
+ y[0][i] = y[0][i - 1] * x[0][i];
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/cumulative_sum.c b/lib/cumulative_sum.c
new file mode 100644
index 0000000..417a37f
--- /dev/null
+++ b/lib/cumulative_sum.c
@@ -0,0 +1,42 @@
+// Cumulative Sum
+//
+// cumulative_sum - cumsum
+//
+// y[i] = x[0] + x[1] + ... + x[i]
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_cumulative_sum_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_cumulative_sum_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_cumulative_sum(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_cumulative_sum_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ if( i == 0 )
+ y[0][i] = x[0][i];
+ else
+ y[0][i] = y[0][i - 1] + x[0][i];
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/cumulative_volume_delta.c b/lib/cumulative_volume_delta.c
new file mode 100644
index 0000000..d76da1c
--- /dev/null
+++ b/lib/cumulative_volume_delta.c
@@ -0,0 +1,44 @@
+// Cumulative Volume Delta
+//
+// cumulative_volume_delta - CVD
+//
+// Running sum of volume weighted by close-open direction
+//
+// Inputs: 5 (OHLCV)
+// Outputs: 1 (CVD)
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_cumulative_volume_delta_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_cumulative_volume_delta_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_cumulative_volume_delta(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_cumulative_volume_delta_p );
+ (void)c;
+ fp_t cum = 0;
+
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t diff = x[3][i] - x[0][i];
+ fp_t sign = ( diff > 0 ) ? 1.0 : ( diff < 0 ) ? -1.0 : 0.0;
+ cum += x[4][i] * sign;
+ y[0][i] = cum;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/cv.c b/lib/cv.c
new file mode 100644
index 0000000..0c06250
--- /dev/null
+++ b/lib/cv.c
@@ -0,0 +1,56 @@
+// ConnorsRSI Volatility
+//
+// cv - CV
+//
+// EMA_fast(H-L) - EMA_slow(H-L), volatility measure
+//
+// Inputs: 5 (OHLCV)
+// Outputs: 1 (CV)
+// Constants: 2 (fast, slow periods)
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ fp_t fast;
+ fp_t slow;
+} ts_cv_params_t;
+
+ts_parameter_t const ts_cv_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 2, .params_size = sizeof(ts_cv_params_t) };
+
+uint_t ts_cv_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? (uint_t)((ts_cv_params_t *)c)->slow : 0 );
+}
+
+int ts_cv(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_cv_p );
+ if ( !c ) return -1;
+ fp_t fast = ((ts_cv_params_t *)c)->fast;
+ fp_t slow = ((ts_cv_params_t *)c)->slow;
+ if (fast == 0 || slow == 0 || n == 0) return -1;
+
+ fp_t a_fast = 1.0 / fast;
+ fp_t a_slow = 1.0 / slow;
+
+ fp_t ema_fast = ts_h( x, 0 ) - ts_l( x, 0 );
+ fp_t ema_slow = ema_fast;
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ fp_t range = ts_h( x, i ) - ts_l( x, i );
+ ema_fast = ema_fast + a_fast * ( range - ema_fast );
+ ema_slow = ema_slow + a_slow * ( range - ema_slow );
+ y[0][i] = ema_fast - ema_slow;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/darvas_box.c b/lib/darvas_box.c
new file mode 100644
index 0000000..573ed1e
--- /dev/null
+++ b/lib/darvas_box.c
@@ -0,0 +1,54 @@
+// Darvas Box
+//
+// darvas_box - DB
+//
+// Upper: highest high over period k
+// Lower: lowest low over period k
+//
+// Inputs: 4 (OHLC)
+// Outputs: 2 (Upper, Lower)
+// Constants: 1 (period)
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_darvas_box_params_t;
+
+ts_parameter_t const ts_darvas_box_p = { .n_inputs = 4, .n_outputs = 2, .n_constants = 1, .params_size = sizeof(ts_darvas_box_params_t) };
+
+uint_t ts_darvas_box_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_darvas_box_params_t *)c)->k > 0 ? ((ts_darvas_box_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_darvas_box(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_darvas_box_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_darvas_box_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t hh = ts_h( x, i - k + 1 );
+ fp_t ll = ts_l( x, i - k + 1 );
+ for( uint_t j = i - k + 1; j <= i; ++j )
+ {
+ if( ts_h( x, j ) > hh ) hh = ts_h( x, j );
+ if( ts_l( x, j ) < ll ) ll = ts_l( x, j );
+ }
+ y[0][i] = hh;
+ y[1][i] = ll;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/dawsn.c b/lib/dawsn.c
new file mode 100644
index 0000000..e77d8b0
--- /dev/null
+++ b/lib/dawsn.c
@@ -0,0 +1,64 @@
+// Dawson Integral
+//
+// dawsn - Dawson function
+//
+// F(x) = e^(-x²) * ∫₀ˣ e^(t²) dt
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_dawsn_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_dawsn_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_dawsn(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_dawsn_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t val = x[0][i];
+ fp_t ax = fabs( val );
+ fp_t y_val;
+
+ if( ax < 3.0 )
+ {
+ fp_t t = ax;
+ fp_t s = t;
+ for( uint_t k = 1; k < 20; ++k )
+ {
+ t *= -2.0 * ax * ax / (fp_t)(2 * k + 1);
+ s += t;
+ }
+ y_val = s;
+ }
+ else
+ {
+ y_val = 1.0 / ( 2.0 * ax + 1.0 / ( 2.0 * ax + 3.0 / ( 2.0 * ax + 5.0 / ( 2.0 * ax ) ) ) );
+ }
+
+ if( val < 0 )
+ y_val = -y_val;
+
+ y[0][i] = y_val;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/decay_exponential.c b/lib/decay_exponential.c
new file mode 100644
index 0000000..97afe36
--- /dev/null
+++ b/lib/decay_exponential.c
@@ -0,0 +1,58 @@
+// Exponential Decay
+//
+// decay_exponential - edecay
+//
+// DESCRIPTION
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+} ts_decay_exponential_params_t;
+
+ts_parameter_t const ts_decay_exponential_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_decay_exponential_params_t) };
+
+
+uint_t ts_decay_exponential_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_decay_exponential_params_t *)c)->k : 0 );
+}
+
+int ts_decay_exponential(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_decay_exponential_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_decay_exponential_params_t *)c)->k;
+ if (k == 0 || n <= k) return -1;
+
+ fp_t a = ( fp_t ) 1.0 - _ts_reciprocal( k );
+
+ for( uint_t i = 0; i < k; ++i )
+ y[0][i] = 0;
+
+ fp_t prev = x[0][0];
+ for( uint_t i = 1; i <= k; ++i )
+ prev = fmax( x[0][i], prev * a );
+ y[0][k] = prev;
+
+ for( uint_t i = k + 1; i < n; ++i )
+ {
+ y[0][i] = fmax( x[0][i], y[0][i - 1] * a );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/decay_linear.c b/lib/decay_linear.c
new file mode 100644
index 0000000..8519d13
--- /dev/null
+++ b/lib/decay_linear.c
@@ -0,0 +1,85 @@
+// Linear Decay
+//
+// decay_linear - decay
+//
+// Propogates the maximum values of a function into the future for at most k periods
+//
+// y_i = max( x_i, y_i-1 - k )
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+} ts_decay_linear_params_t;
+
+ts_parameter_t const ts_decay_linear_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_decay_linear_params_t) };
+
+
+uint_t ts_decay_linear_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_decay_linear_params_t *)c)->k : 0 );
+}
+
+int ts_decay_linear(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_decay_linear_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_decay_linear_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for (uint_t zi = 0; zi < k; ++zi) y[0][zi] = 0;
+
+ uint_t xi = 1;
+
+ fp_t prev = x[0][0];
+ fp_t r = prev / k;
+
+ for( ; xi < k; xi++ )
+ {
+ prev -= r;
+
+ if( x[0][xi] > prev )
+ {
+ prev = x[0][xi];
+ r = x[0][xi] / k;
+ }
+ else
+ {
+ prev = fmax( prev, 0 );
+ }
+ }
+
+ for( uint_t yi = k; xi < n; xi++, yi++ )
+ {
+ fp_t curr = prev - r;
+
+ if( x[0][xi] > curr )
+ {
+ curr = x[0][xi];
+ r = x[0][xi] / k;
+ }
+ else
+ {
+ curr = fmax( curr, 0 );
+ }
+
+ y[0][yi] = curr;
+ prev = curr;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/degree_radian.c b/lib/degree_radian.c
new file mode 100644
index 0000000..821b67f
--- /dev/null
+++ b/lib/degree_radian.c
@@ -0,0 +1,39 @@
+// Degrees to Radians
+//
+// degree_radian - deg2rad
+//
+// y = x * PI / 180
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_degree_radian_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_degree_radian_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_degree_radian(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_degree_radian_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = x[0][i] * PI / 180.0;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/delta.c b/lib/delta.c
new file mode 100644
index 0000000..02ccb66
--- /dev/null
+++ b/lib/delta.c
@@ -0,0 +1,50 @@
+// Delta
+//
+// delta - delta
+//
+// Calculates the delta between the current value and the value k samples ago
+//
+// y_i = x_i - x_i-k
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+} ts_delta_params_t;
+
+ts_parameter_t const ts_delta_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_delta_params_t) };
+
+
+uint_t ts_delta_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_delta_params_t *)c)->k : 0 );
+}
+
+int ts_delta(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_delta_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_delta_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ uint_t xi = k;
+
+ for( ; xi < n; xi++ )
+ {
+ y[0][xi] = x[0][xi] - x[0][xi - k];
+ }
+ return 0;
+}
+
+
diff --git a/lib/deviation_average.c b/lib/deviation_average.c
new file mode 100644
index 0000000..5ac7109
--- /dev/null
+++ b/lib/deviation_average.c
@@ -0,0 +1,78 @@
+// Moving Mean Deviation
+//
+// mean_deviation - md
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+} ts_deviation_average_params_t;
+
+ts_parameter_t const ts_deviation_average_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_deviation_average_params_t) };
+
+
+uint_t ts_deviation_average_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_deviation_average_params_t *)c)->k > 0 ? ((ts_deviation_average_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_deviation_average(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_deviation_average_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_deviation_average_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ uint_t xi = 0;
+ uint_t yi = k - 1;
+
+ fp_t mean = 0;
+
+ for( ; xi < k; ++xi )
+ {
+ mean += x[0][xi];
+ }
+
+ fp_t rk = _ts_reciprocal( k );
+ mean = rk * mean;
+
+ {
+ fp_t sum = 0;
+ for( uint_t i = 0; i < k; i++ )
+ {
+ fp_t tmp = x[0][i] - mean;
+ sum += fabs( tmp );
+ }
+ y[0][yi] = sum * rk;
+ }
+
+ for( xi = k, yi = k; xi < n; ++xi, ++yi )
+ {
+ mean = mean + ( x[0][xi] - x[0][xi - k] ) * rk;
+ fp_t sum = 0;
+
+ for( uint_t i = xi - k + 1; i <= xi; i++ )
+ {
+ fp_t tmp = x[0][i] - mean;
+ sum += fabs( tmp );
+ }
+
+ y[0][yi] = sum * rk;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/deviation_standard.c b/lib/deviation_standard.c
new file mode 100644
index 0000000..a2a0a76
--- /dev/null
+++ b/lib/deviation_standard.c
@@ -0,0 +1,70 @@
+// Moving Standard Deviation
+//
+// standard_deviation - sd
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+} ts_deviation_standard_params_t;
+
+ts_parameter_t const ts_deviation_standard_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_deviation_standard_params_t) };
+
+
+uint_t ts_deviation_standard_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_deviation_standard_params_t *)c)->k > 0 ? ((ts_deviation_standard_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_deviation_standard(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_deviation_standard_p );
+ if ( !c ) return -1;
+ TS_CHECK_INPUT(x, x_n, n);
+ uint_t k = ((ts_deviation_standard_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ uint_t xi = 0;
+ uint_t yi = k;
+
+ fp_t mean = 0; // Mean
+ fp_t mean2 = 0; // Mean of squares
+
+ for( ; xi < k; ++xi )
+ {
+ mean += x[0][xi];
+ mean2 += _ts_square( x[0][xi] );
+ }
+
+ mean /= k;
+ mean2 /= k;
+
+ fp_t variance = mean2 - _ts_square( mean );
+ y[0][k - 1] = variance >= 0 ? sqrt( variance ) : 0;
+
+ fp_t rk = _ts_reciprocal( k );
+
+ for( ; xi < n; ++xi, ++yi )
+ {
+ mean = mean + ( x[0][xi] - x[0][xi - k] ) * rk;
+ mean2 = mean2 + ( _ts_square( x[0][xi] ) - _ts_square( x[0][xi - k] ) ) * rk;
+
+ variance = mean2 - _ts_square( mean );
+ y[0][yi] = variance >= 0 ? sqrt( variance ) : 0;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/deviation_standard_average_exponential.c b/lib/deviation_standard_average_exponential.c
new file mode 100644
index 0000000..8c484f5
--- /dev/null
+++ b/lib/deviation_standard_average_exponential.c
@@ -0,0 +1,57 @@
+// Exponential Moving Standard Deviation
+//
+// standard_deviation_exponential - sde
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ fp_t a;
+} ts_deviation_standard_average_exponential_params_t;
+
+ts_parameter_t const ts_deviation_standard_average_exponential_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_deviation_standard_average_exponential_params_t) };
+
+
+uint_t ts_deviation_standard_average_exponential_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_deviation_standard_average_exponential(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_deviation_standard_average_exponential_p );
+ if ( !c ) return -1;
+ if (n == 0) return -1;
+
+ fp_t a = ((ts_deviation_standard_average_exponential_params_t *)c)->a;
+ if (a == 0) return -1;
+
+ y[0][0] = x[0][0];
+
+ fp_t delta;
+ fp_t ema = x[0][0];
+ fp_t variance = 0;
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ delta = x[0][i] - ema;
+ ema = ema + a * delta;
+ variance = ( 1 - a ) * variance + a * _ts_square( delta );
+ y[0][i] = variance >= 0 ? sqrt( variance ) : 0;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/deviation_vector.c b/lib/deviation_vector.c
new file mode 100644
index 0000000..4f10037
--- /dev/null
+++ b/lib/deviation_vector.c
@@ -0,0 +1,61 @@
+// Vector Standard Deviation
+//
+// deviation_vector - std_dev
+//
+// Rolling population standard deviation over period k
+//
+// Inputs: 1
+// Outputs: 1 (stddev)
+// Constants: 1 (period)
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_deviation_vector_params_t;
+
+ts_parameter_t const ts_deviation_vector_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_deviation_vector_params_t) };
+
+uint_t ts_deviation_vector_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_deviation_vector_params_t *)c)->k > 0 ? ((ts_deviation_vector_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_deviation_vector(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_deviation_vector_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_deviation_vector_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for (uint_t i = 0; i < n; ++i)
+ {
+ if (i < k - 1)
+ {
+ y[0][i] = 0;
+ }
+ else
+ {
+ fp_t sum = 0;
+ fp_t sum2 = 0;
+ for (uint_t j = i - k + 1; j <= i; ++j)
+ {
+ sum += x[0][j];
+ sum2 += x[0][j] * x[0][j];
+ }
+ fp_t mean = sum / k;
+ fp_t variance = sum2 / k - mean * mean;
+ y[0][i] = variance >= 0 ? sqrt(variance) : 0;
+ }
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/di_minus.c b/lib/di_minus.c
new file mode 100644
index 0000000..95b0559
--- /dev/null
+++ b/lib/di_minus.c
@@ -0,0 +1,69 @@
+// Minus Directional Indicator
+//
+// di_minus - DI-
+//
+// 100 * EMA(-DM) / (EMA(+DM) + EMA(-DM)), measures downward pressure
+//
+// Inputs: 4 (OHLC)
+// Outputs: 1 (DI-)
+// Constants: 1 (period)
+
+#include <math.h>
+#include <stdlib.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_di_minus_params_t;
+
+ts_parameter_t const ts_di_minus_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_di_minus_params_t) };
+
+uint_t ts_di_minus_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_di_minus_params_t *)c)->k : 0 );
+}
+
+int ts_di_minus(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_di_minus_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_di_minus_params_t *)c)->k;
+ if (k == 0 || n < 2) return -1;
+ fp_t *ema_minus = calloc( n, sizeof( fp_t ) );
+ fp_t *ema_plus = calloc( n, sizeof( fp_t ) );
+ if( !ema_minus || !ema_plus ) { free( ema_minus ); free( ema_plus ); return -1; }
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ fp_t hdmd = ts_h( x, i ) - ts_h( x, i - 1 );
+ fp_t ldml = ts_l( x, i - 1 ) - ts_l( x, i );
+
+ fp_t minus_dm = ldml > hdmd && ldml > 0 ? ldml : 0;
+ fp_t plus_dm = hdmd > ldml && hdmd > 0 ? hdmd : 0;
+
+ ema_minus[i] = i == 1 ? minus_dm
+ : ema_minus[i - 1] + ( 1.0 / k ) * ( minus_dm - ema_minus[i - 1] );
+ ema_plus[i] = i == 1 ? plus_dm
+ : ema_plus[i - 1] + ( 1.0 / k ) * ( plus_dm - ema_plus[i - 1] );
+ }
+
+ for (uint_t j = 0; j < n && j < k; ++j) y[0][j] = 0;
+
+ for( uint_t i = k; i < n; ++i )
+ {
+ fp_t denom = ema_minus[i] + ema_plus[i];
+ y[0][i] = denom != 0 ? 100.0 * ema_minus[i] / denom : 0;
+ }
+
+ free( ema_minus );
+ free( ema_plus );
+ return 0;
+}
+
+
+
diff --git a/lib/di_plus.c b/lib/di_plus.c
new file mode 100644
index 0000000..fb3c076
--- /dev/null
+++ b/lib/di_plus.c
@@ -0,0 +1,69 @@
+// Plus Directional Indicator
+//
+// di_plus - DI+
+//
+// 100 * EMA(+DM) / (EMA(+DM) + EMA(-DM)), measures upward pressure
+//
+// Inputs: 4 (OHLC)
+// Outputs: 1 (DI+)
+// Constants: 1 (period)
+
+#include <math.h>
+#include <stdlib.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_di_plus_params_t;
+
+ts_parameter_t const ts_di_plus_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_di_plus_params_t) };
+
+uint_t ts_di_plus_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_di_plus_params_t *)c)->k : 0 );
+}
+
+int ts_di_plus(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_di_plus_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_di_plus_params_t *)c)->k;
+ if (k == 0 || n < 2) return -1;
+ fp_t *ema_plus = calloc( n, sizeof( fp_t ) );
+ fp_t *ema_minus = calloc( n, sizeof( fp_t ) );
+ if( !ema_plus || !ema_minus ) { free( ema_plus ); free( ema_minus ); return -1; }
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ fp_t hdmd = ts_h( x, i ) - ts_h( x, i - 1 );
+ fp_t ldml = ts_l( x, i - 1 ) - ts_l( x, i );
+
+ fp_t plus_dm = hdmd > ldml && hdmd > 0 ? hdmd : 0;
+ fp_t minus_dm = ldml > hdmd && ldml > 0 ? ldml : 0;
+
+ ema_plus[i] = i == 1 ? plus_dm
+ : ema_plus[i - 1] + ( 1.0 / k ) * ( plus_dm - ema_plus[i - 1] );
+ ema_minus[i] = i == 1 ? minus_dm
+ : ema_minus[i - 1] + ( 1.0 / k ) * ( minus_dm - ema_minus[i - 1] );
+ }
+
+ for (uint_t j = 0; j < n && j < k; ++j) y[0][j] = 0;
+
+ for( uint_t i = k; i < n; ++i )
+ {
+ fp_t denom = ema_plus[i] + ema_minus[i];
+ y[0][i] = denom != 0 ? 100.0 * ema_plus[i] / denom : 0;
+ }
+
+ free( ema_plus );
+ free( ema_minus );
+ return 0;
+}
+
+
+
diff --git a/lib/digamma.c b/lib/digamma.c
new file mode 100644
index 0000000..267fefc
--- /dev/null
+++ b/lib/digamma.c
@@ -0,0 +1,72 @@
+// Digamma Function
+//
+// digamma - ψ(z)
+//
+// Logarithmic derivative of the gamma function: ψ(z) = Γ'(z) / Γ(z)
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+static fp_t digamma_impl( fp_t z )
+{
+ if (z > 7.0)
+ {
+ fp_t sum = log(z) - 1.0 / (2.0 * z) - 1.0 / (12.0 * z * z);
+ return sum;
+ }
+
+ fp_t sum = 0.0;
+ if (z > 0.0 && z < 7.0)
+ {
+ fp_t tmp = z;
+ while (tmp < 7.0)
+ {
+ sum -= 1.0 / tmp;
+ tmp += 1.0;
+ }
+ return sum + digamma_impl(tmp);
+ }
+ else if (z == 0.0)
+ {
+ return -INFINITY;
+ }
+ else if (z < 0.0)
+ {
+ fp_t n = floor(z);
+ fp_t frac = z - n;
+ return digamma_impl(1.0 - frac) - PI / tan(PI * frac);
+ }
+
+ return digamma_impl(z + 1.0) - 1.0 / z;
+}
+
+
+ts_parameter_t const ts_digamma_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_digamma_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_digamma(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_digamma_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = digamma_impl( x[0][i] );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/disparity_index.c b/lib/disparity_index.c
new file mode 100644
index 0000000..ebf5e0d
--- /dev/null
+++ b/lib/disparity_index.c
@@ -0,0 +1,58 @@
+// Disparity Index
+//
+// disparity_index - DI
+//
+// (Price - SMA) / SMA * 100, deviation from moving average
+//
+// Inputs: 1
+// Outputs: 1 (DI %)
+// Constants: 1 (period)
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_disparity_index_params_t;
+
+ts_parameter_t const ts_disparity_index_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_disparity_index_params_t) };
+
+uint_t ts_disparity_index_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_disparity_index_params_t *)c)->k > 0 ? ((ts_disparity_index_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_disparity_index(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_disparity_index_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_disparity_index_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ fp_t sum = 0;
+ for ( uint_t i = 0; i < k; ++i )
+ {
+ sum += x[0][i];
+ }
+
+ fp_t sma = sum / ( fp_t )k;
+
+ for ( uint_t i = k - 1; i < n; ++i )
+ {
+ if ( i >= k )
+ {
+ sma = sma + ( x[0][i] - x[0][i - k] ) / ( fp_t )k;
+ }
+
+ y[0][i] = sma != 0 ? ( x[0][i] - sma ) / sma * 100.0 : 0;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/distance.c b/lib/distance.c
new file mode 100644
index 0000000..5e473f8
--- /dev/null
+++ b/lib/distance.c
@@ -0,0 +1,39 @@
+// Euclidean Distance
+//
+// distance - dist
+//
+// √(x² + y²), 2D Euclidean distance from origin
+//
+// Inputs: 2
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_distance_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_distance_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_distance(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_distance_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = sqrt( x[0][i] * x[0][i] + x[1][i] * x[1][i] );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/division.c b/lib/division.c
new file mode 100644
index 0000000..e132a77
--- /dev/null
+++ b/lib/division.c
@@ -0,0 +1,41 @@
+// Division
+//
+// division - div
+//
+// f( x_0, x_1 ) = x_0 / x_1
+//
+// X_n - 2
+// Y_n - 1
+// C_n - 0
+
+
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_division_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_division_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_division(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_division_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = x[1][i] == 0.0 ? 0.0 : x[0][i] / x[1][i];
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/donchian_channel.c b/lib/donchian_channel.c
new file mode 100644
index 0000000..760c396
--- /dev/null
+++ b/lib/donchian_channel.c
@@ -0,0 +1,60 @@
+// Donchian Channel
+//
+// donchian_channel - DC
+//
+// Upper: highest high over k periods
+// Lower: lowest low over k periods
+// Middle: (Upper + Lower) / 2
+//
+// Inputs: 5 (OHLCV)
+// Outputs: 3 (Upper, Lower, Middle)
+// Constants: 1 (period)
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_donchian_channel_params_t;
+
+ts_parameter_t const ts_donchian_channel_p = { .n_inputs = 4, .n_outputs = 3, .n_constants = 1, .params_size = sizeof(ts_donchian_channel_params_t) };
+
+uint_t ts_donchian_channel_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_donchian_channel_params_t *)c)->k > 0 ? ((ts_donchian_channel_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_donchian_channel(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_donchian_channel_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_donchian_channel_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for ( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t upper = ts_h( x, i - k + 1 );
+ fp_t lower = ts_l( x, i - k + 1 );
+
+ for ( uint_t j = i - k + 1; j <= i; ++j )
+ {
+ if ( ts_h( x, j ) > upper )
+ upper = ts_h( x, j );
+ if ( ts_l( x, j ) < lower )
+ lower = ts_l( x, j );
+ }
+
+ y[0][i] = upper;
+ y[1][i] = lower;
+ y[2][i] = ( upper + lower ) / 2.0;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/donchian_width.c b/lib/donchian_width.c
new file mode 100644
index 0000000..c69936f
--- /dev/null
+++ b/lib/donchian_width.c
@@ -0,0 +1,56 @@
+// Donchian Width
+//
+// donchian_width - DW
+//
+// Highest high - lowest low over k periods
+//
+// Inputs: 5 (OHLCV)
+// Outputs: 1 (Width)
+// Constants: 1 (period)
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_donchian_width_params_t;
+
+ts_parameter_t const ts_donchian_width_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_donchian_width_params_t) };
+
+uint_t ts_donchian_width_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_donchian_width_params_t *)c)->k > 0 ? ((ts_donchian_width_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_donchian_width(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_donchian_width_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_donchian_width_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for ( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t upper = ts_h( x, i - k + 1 );
+ fp_t lower = ts_l( x, i - k + 1 );
+
+ for ( uint_t j = i - k + 1; j <= i; ++j )
+ {
+ if ( ts_h( x, j ) > upper )
+ upper = ts_h( x, j );
+ if ( ts_l( x, j ) < lower )
+ lower = ts_l( x, j );
+ }
+
+ y[0][i] = upper - lower;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/dpo.c b/lib/dpo.c
new file mode 100644
index 0000000..5bf069b
--- /dev/null
+++ b/lib/dpo.c
@@ -0,0 +1,78 @@
+// Detrended Price Oscillator
+//
+// dpo - DPO
+//
+// Price[t] - SMA[k][t - k/2 - 1], centerless moving average
+//
+// Inputs: 1
+// Outputs: 1 (DPO)
+// Constants: 1 (period)
+
+#include <math.h>
+#include <stdlib.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_dpo_params_t;
+
+ts_parameter_t const ts_dpo_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_dpo_params_t) };
+
+uint_t ts_dpo_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_dpo_params_t *)c)->k / 2 + 1 : 0 );
+}
+
+int ts_dpo(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_dpo_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_dpo_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ uint_t sma_period = k;
+ uint_t offset = k / 2 + 1;
+
+ if (n < sma_period) return -1;
+
+ fp_t sum = 0;
+ for ( uint_t i = 0; i < sma_period; ++i )
+ {
+ sum += x[0][i];
+ }
+
+ fp_t *sma_values = calloc( n, sizeof( fp_t ) );
+ if ( !sma_values ) return -1;
+
+ for (uint_t i = 0; i < offset; ++i) y[0][i] = 0;
+
+ uint_t sma_idx = 0;
+
+ for ( uint_t i = sma_period - 1; i < n; ++i )
+ {
+ if ( i == sma_period - 1 )
+ {
+ sma_values[sma_idx] = sum / ( fp_t )sma_period;
+ }
+ else
+ {
+ sma_values[sma_idx] = sma_values[sma_idx - 1] + ( x[0][i] - x[0][i - sma_period] ) / ( fp_t )sma_period;
+ }
+ sma_idx++;
+
+ if ( i >= offset )
+ {
+ y[0][i] = x[0][i] - sma_values[i - offset];
+ }
+ }
+
+ free( sma_values );
+ return 0;
+}
+
+
diff --git a/lib/dx.c b/lib/dx.c
new file mode 100644
index 0000000..b4552fa
--- /dev/null
+++ b/lib/dx.c
@@ -0,0 +1,79 @@
+// Directional Movement Index
+//
+// dx - DX
+//
+// 100 * |DI+ - DI-| / (DI+ + DI-), trend strength without smoothing
+//
+// Inputs: 4 (OHLC)
+// Outputs: 1 (DX)
+// Constants: 1 (period)
+
+#include <math.h>
+#include <stdlib.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_dx_params_t;
+
+ts_parameter_t const ts_dx_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_dx_params_t) };
+
+uint_t ts_dx_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_dx_params_t *)c)->k : 0 );
+}
+
+int ts_dx(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_dx_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_dx_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ fp_t *plus_dm_arr = calloc( n, sizeof( fp_t ) );
+ fp_t *minus_dm_arr = calloc( n, sizeof( fp_t ) );
+ fp_t *tr_arr = calloc( n, sizeof( fp_t ) );
+ if( !plus_dm_arr || !minus_dm_arr || !tr_arr ) { free( plus_dm_arr ); free( minus_dm_arr ); free( tr_arr ); return -1; }
+
+ y[0][0] = 0;
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ fp_t hdmd = ts_h( x, i ) - ts_h( x, i - 1 );
+ fp_t ldml = ts_l( x, i - 1 ) - ts_l( x, i );
+
+ fp_t plus_dm = hdmd > ldml && hdmd > 0 ? hdmd : 0;
+ fp_t minus_dm = ldml > hdmd && ldml > 0 ? ldml : 0;
+
+ fp_t tr = fmax( ts_h( x, i ) - ts_l( x, i ),
+ fmax( fabs( ts_h( x, i ) - ts_c( x, i - 1 ) ),
+ fabs( ts_l( x, i ) - ts_c( x, i - 1 ) ) ) );
+
+ plus_dm_arr[i] = i == 1 ? plus_dm
+ : plus_dm_arr[i - 1] + ( 1.0 / k ) * ( plus_dm - plus_dm_arr[i - 1] );
+ minus_dm_arr[i] = i == 1 ? minus_dm
+ : minus_dm_arr[i - 1] + ( 1.0 / k ) * ( minus_dm - minus_dm_arr[i - 1] );
+ tr_arr[i] = i == 1 ? tr
+ : tr_arr[i - 1] + ( 1.0 / k ) * ( tr - tr_arr[i - 1] );
+ }
+
+ for( uint_t i = k; i < n; ++i )
+ {
+ fp_t di_plus = tr_arr[i] != 0 ? 100.0 * plus_dm_arr[i] / tr_arr[i] : 0;
+ fp_t di_minus = tr_arr[i] != 0 ? 100.0 * minus_dm_arr[i] / tr_arr[i] : 0;
+ fp_t sum = di_plus + di_minus;
+ y[0][i] = sum != 0 ? 100.0 * fabs( di_plus - di_minus ) / sum : 0;
+ }
+
+ free( plus_dm_arr );
+ free( minus_dm_arr );
+ free( tr_arr );
+ return 0;
+}
+
+
diff --git a/lib/ease_of_movement.c b/lib/ease_of_movement.c
new file mode 100644
index 0000000..d16f32c
--- /dev/null
+++ b/lib/ease_of_movement.c
@@ -0,0 +1,71 @@
+// Ease of Movement
+//
+// ease_of_movement - EOM
+//
+// SMA of (midpoint change) / (volume / range), volume-based momentum
+//
+// Inputs: 5 (OHLCV)
+// Outputs: 1 (EOM)
+// Constants: 1 (period)
+
+#include <stdlib.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_ease_of_movement_params_t;
+
+ts_parameter_t const ts_ease_of_movement_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_ease_of_movement_params_t) };
+
+uint_t ts_ease_of_movement_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 3 );
+}
+
+int ts_ease_of_movement(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_ease_of_movement_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_ease_of_movement_params_t *)c)->k;
+ if (k == 0 || n <= k) return -1;
+
+ fp_t *eom = calloc( n, sizeof( fp_t ) );
+ if( !eom ) return -1;
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ fp_t hl = ts_h( x, i ) - ts_l( x, i );
+ if( hl == 0 ) continue;
+ fp_t br = ts_v( x, i ) / hl;
+ if( br == 0 ) continue;
+ fp_t cur_mid = ( ts_h( x, i ) + ts_l( x, i ) ) / 2.0;
+ fp_t prev_mid = ( ts_h( x, i - 1 ) + ts_l( x, i - 1 ) ) / 2.0;
+ eom[i] = ( cur_mid - prev_mid ) / br;
+ }
+
+ {
+ for (uint_t i = 0; i < k; ++i) y[0][i] = 0;
+
+ fp_t sum = 0;
+ for( uint_t i = 1; i <= k; ++i )
+ {
+ sum += eom[i];
+ }
+ y[0][k] = sum / (fp_t)k;
+ for( uint_t i = k + 1; i < n; ++i )
+ {
+ sum = sum - eom[i - k] + eom[i];
+ y[0][i] = sum / (fp_t)k;
+ }
+ }
+
+ free( eom );
+ return 0;
+}
+
+
diff --git a/lib/elder_force_index.c b/lib/elder_force_index.c
new file mode 100644
index 0000000..23cce66
--- /dev/null
+++ b/lib/elder_force_index.c
@@ -0,0 +1,52 @@
+// Elder Force Index
+//
+// elder_force_index - EFI
+//
+// EMA((Close - PrevClose) * Volume), volume-weighted price change
+//
+// Inputs: 5 (OHLCV)
+// Outputs: 1 (EFI)
+// Constants: 1 (period)
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_elder_force_index_params_t;
+
+ts_parameter_t const ts_elder_force_index_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_elder_force_index_params_t) };
+
+uint_t ts_elder_force_index_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 1 );
+}
+
+int ts_elder_force_index(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_elder_force_index_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_elder_force_index_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+ fp_t a = 1.0 / k;
+
+ fp_t fi = 0;
+ if ( n > 1 )
+ fi = ( ts_c( x, 1 ) - ts_c( x, 0 ) ) * ts_v( x, 1 );
+ y[0][0] = fi;
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ fi = ( ts_c( x, i ) - ts_c( x, i - 1 ) ) * ts_v( x, i );
+ y[0][i] = y[0][i - 1] + a * ( fi - y[0][i - 1] );
+ }
+
+ return 0;
+}
+
+
diff --git a/lib/elder_impulse.c b/lib/elder_impulse.c
new file mode 100644
index 0000000..d629e7c
--- /dev/null
+++ b/lib/elder_impulse.c
@@ -0,0 +1,70 @@
+// Elder Impulse System
+//
+// elder_impulse - EIS
+//
+// Impulse: Price - SMA(k), Trend: SMA(Impulse, k)
+//
+// Inputs: 1
+// Outputs: 2 (Impulse, Trend)
+// Constants: 1 (period)
+
+#include <stdlib.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_elder_impulse_params_t;
+
+ts_parameter_t const ts_elder_impulse_p = { .n_inputs = 1, .n_outputs = 2, .n_constants = 1, .params_size = sizeof(ts_elder_impulse_params_t) };
+
+uint_t ts_elder_impulse_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? 2 * ((ts_elder_impulse_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_elder_impulse(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_elder_impulse_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_elder_impulse_params_t *)c)->k;
+ if (k == 0 || n < 2 * k - 1) return -1;
+
+ fp_t *sma = malloc( n * sizeof( fp_t ) );
+ fp_t *impulse = malloc( n * sizeof( fp_t ) );
+ if ( !sma || !impulse ) { free( sma ); free( impulse ); return -1; }
+
+ for (uint_t i = 0; i < 2 * k - 1; ++i) { y[0][i] = 0; y[1][i] = 0; }
+
+ for ( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t sum = 0;
+ for ( uint_t j = 0; j < k; ++j )
+ {
+ sum += x[0][i - j];
+ }
+ sma[i] = sum / (fp_t)k;
+ impulse[i] = x[0][i] - sma[i];
+ }
+
+ for ( uint_t i = 2 * k - 1; i < n; ++i )
+ {
+ y[0][i] = impulse[i];
+
+ fp_t sum = 0;
+ for ( uint_t j = 0; j < k; ++j )
+ {
+ sum += impulse[i - j];
+ }
+ y[1][i] = sum / (fp_t)k;
+ }
+
+ free( sma ); free( impulse );
+ return 0;
+}
+
+
diff --git a/lib/elder_ray.c b/lib/elder_ray.c
new file mode 100644
index 0000000..a0f330f
--- /dev/null
+++ b/lib/elder_ray.c
@@ -0,0 +1,58 @@
+// Elder Ray Index
+//
+// elder_ray - ERI
+//
+// Bull Power: High - EMA(Close)
+// Bear Power: Low - EMA(Close)
+//
+// Inputs: 4 (OHLC)
+// Outputs: 2 (Bull Power, Bear Power)
+// Constants: 1 (EMA period)
+
+#include <math.h>
+#include <stdlib.h>
+#include "ts.h"
+
+typedef struct {
+ fp_t k;
+} ts_elder_ray_params_t;
+
+ts_parameter_t const ts_elder_ray_p = { .n_inputs = 4, .n_outputs = 2, .n_constants = 1, .params_size = sizeof(ts_elder_ray_params_t) };
+
+uint_t ts_elder_ray_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_elder_ray(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_elder_ray_p );
+ if ( !c ) return -1;
+ fp_t k = ((ts_elder_ray_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+ fp_t a = 1.0 / k;
+ fp_t *ema = calloc( n, sizeof( fp_t ) );
+ if( !ema ) return -1;
+
+ ema[0] = ts_c( x, 0 );
+
+ for( uint_t i = 0; i < n; ++i )
+ {
+ if( i > 0 )
+ {
+ ema[i] = ema[i - 1] + a * ( ts_c( x, i ) - ema[i - 1] );
+ }
+ y[0][i] = ts_h( x, i ) - ema[i];
+ y[1][i] = ts_l( x, i ) - ema[i];
+ }
+
+ free( ema );
+ return 0;
+}
+
+
diff --git a/lib/ellint_1.c b/lib/ellint_1.c
new file mode 100644
index 0000000..8e0bab7
--- /dev/null
+++ b/lib/ellint_1.c
@@ -0,0 +1,65 @@
+// Complete Elliptic Integral of the First Kind
+//
+// ellint_1 - K(m)
+//
+// K(m) = ∫₀^(π/2) dθ / √(1 - m*sin²(θ)), computed via AGM
+//
+// Inputs: 1 (parameter m)
+// Outputs: 1 (K(m))
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_ellint_1_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_ellint_1_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_ellint_1(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_ellint_1_p );
+ (void)c;
+ /* Complete elliptic integral of the first kind K(m) */
+ for (uint_t i = 0; i < n; ++i)
+ {
+ fp_t m = x[0][i];
+ if (m <= 0.0)
+ {
+ y[0][i] = PI / 2.0;
+ }
+ else if (m >= 1.0)
+ {
+ y[0][i] = INFINITY;
+ }
+ else
+ {
+ /* Arithmetic-geometric mean method */
+ fp_t a = 1.0;
+ fp_t b = sqrt(1.0 - m);
+ fp_t cn = (a - b) / 2.0;
+ for (uint_t j = 0; j < 30; ++j)
+ {
+ fp_t a_new = (a + b) / 2.0;
+ fp_t b_new = sqrt(a * b);
+ fp_t cn_new = (a_new - b_new) / 2.0;
+ a = a_new;
+ b = b_new;
+ cn = cn_new;
+ if (cn / a < 1e-15) break;
+ }
+ y[0][i] = PI / (2.0 * a);
+ }
+ }
+ return 0;
+}
+
+
diff --git a/lib/ellint_2.c b/lib/ellint_2.c
new file mode 100644
index 0000000..59660cd
--- /dev/null
+++ b/lib/ellint_2.c
@@ -0,0 +1,64 @@
+// Complete Elliptic Integral of the Second Kind
+//
+// ellint_2 - E(m)
+//
+// E(m) = ∫₀^(π/2) √(1 - m*sin²(θ)) dθ, computed via AGM
+//
+// Inputs: 1 (parameter m)
+// Outputs: 1 (E(m))
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_ellint_2_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_ellint_2_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_ellint_2(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_ellint_2_p );
+ (void)c;
+ for (uint_t i = 0; i < n; ++i)
+ {
+ fp_t m = x[0][i];
+ if (m <= 0.0)
+ {
+ y[0][i] = PI / 2.0;
+ }
+ else if (m >= 1.0)
+ {
+ y[0][i] = 1.0;
+ }
+ else
+ {
+ fp_t a = 1.0;
+ fp_t b = sqrt(1.0 - m);
+ fp_t s = 0.0;
+ fp_t scale = 0.5;
+ for (uint_t j = 0; j < 30; ++j)
+ {
+ fp_t a_new = (a + b) / 2.0;
+ fp_t b_new = sqrt(a * b);
+ fp_t c_new = (a - b) / 2.0;
+ s += scale * c_new * c_new;
+ a = a_new;
+ b = b_new;
+ scale *= 2.0;
+ if (c_new < 1e-15) break;
+ }
+ y[0][i] = (PI / (2.0 * a)) * (1.0 - s);
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/ema_variable.c b/lib/ema_variable.c
new file mode 100644
index 0000000..c20fee2
--- /dev/null
+++ b/lib/ema_variable.c
@@ -0,0 +1,48 @@
+// Variable EMA
+//
+// ema_variable - EMA
+//
+// EMA with alpha = 2/(k+1): y = prev + alpha * (x - prev)
+//
+// Inputs: 1
+// Outputs: 1 (EMA)
+// Constants: 1 (period)
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ fp_t k;
+} ts_ema_variable_params_t;
+
+ts_parameter_t const ts_ema_variable_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_ema_variable_params_t) };
+
+uint_t ts_ema_variable_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_ema_variable(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_ema_variable_p );
+ if ( !c ) return -1;
+ fp_t k = ((ts_ema_variable_params_t *)c)->k;
+ if (k <= 0) return -1;
+ if (n == 0) return -1;
+
+ fp_t alpha = 2.0 / (k + 1.0);
+ y[0][0] = x[0][0];
+
+ for (uint_t i = 1; i < n; ++i)
+ {
+ y[0][i] = y[0][i - 1] + alpha * (x[0][i] - y[0][i - 1]);
+ }
+ return 0;
+}
+
+
diff --git a/lib/envelope.c b/lib/envelope.c
new file mode 100644
index 0000000..1ddda0c
--- /dev/null
+++ b/lib/envelope.c
@@ -0,0 +1,60 @@
+// Price Envelope
+//
+// envelope - ENV
+//
+// Upper: SMA * (1 + pct), Lower: SMA * (1 - pct)
+//
+// Inputs: 1
+// Outputs: 2 (Upper, Lower)
+// Constants: 2 (period, percentage)
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+ fp_t pct;
+} ts_envelope_params_t;
+
+ts_parameter_t const ts_envelope_p = { .n_inputs = 1, .n_outputs = 2, .n_constants = 2, .params_size = sizeof(ts_envelope_params_t) };
+
+uint_t ts_envelope_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_envelope_params_t *)c)->k > 0 ? ((ts_envelope_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_envelope(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_envelope_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_envelope_params_t *)c)->k;
+ fp_t pct = ((ts_envelope_params_t *)c)->pct;
+ if (k == 0 || n < k) return -1;
+
+ fp_t sum = 0;
+ for ( uint_t i = 0; i < k; ++i )
+ {
+ sum += x[0][i];
+ }
+
+ fp_t sma = sum / ( fp_t )k;
+
+ for ( uint_t i = k - 1; i < n; ++i )
+ {
+ if ( i >= k )
+ {
+ sma = sma + ( x[0][i] - x[0][i - k] ) / ( fp_t )k;
+ }
+
+ y[0][i] = sma * ( 1.0 + pct );
+ y[1][i] = sma * ( 1.0 - pct );
+ }
+ return 0;
+}
+
+
diff --git a/lib/erf.c b/lib/erf.c
new file mode 100644
index 0000000..54bf625
--- /dev/null
+++ b/lib/erf.c
@@ -0,0 +1,38 @@
+// Error Function
+//
+// erf - error function
+//
+// erf(x) = (2/√π) * ∫₀ˣ e^(-t²) dt
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_erf_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_erf_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_erf(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_erf_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = erf( x[0][i] );
+ }
+ return 0;
+}
+
+
diff --git a/lib/erfc.c b/lib/erfc.c
new file mode 100644
index 0000000..e6e1492
--- /dev/null
+++ b/lib/erfc.c
@@ -0,0 +1,38 @@
+// Complementary Error Function
+//
+// erfc - complementary error function
+//
+// erfc(x) = 1 - erf(x)
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_erfc_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_erfc_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_erfc(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_erfc_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = erfc( x[0][i] );
+ }
+ return 0;
+}
+
+
diff --git a/lib/euclidean_norm.c b/lib/euclidean_norm.c
new file mode 100644
index 0000000..6de406d
--- /dev/null
+++ b/lib/euclidean_norm.c
@@ -0,0 +1,38 @@
+// Euclidean Norm
+//
+// euclidean_norm - L2 norm
+//
+// √(x² + y²), 2D vector magnitude
+//
+// Inputs: 2
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_euclidean_norm_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_euclidean_norm_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_euclidean_norm(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_euclidean_norm_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = hypot( x[0][i], x[1][i] );
+ }
+ return 0;
+}
+
+
diff --git a/lib/exp10.c b/lib/exp10.c
new file mode 100644
index 0000000..b4bb568
--- /dev/null
+++ b/lib/exp10.c
@@ -0,0 +1,40 @@
+// Base-10 Exponential
+//
+// exp10 - 10^x
+//
+// y = 10^x
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_exp10_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_exp10_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_exp10(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_exp10_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = pow( 10.0, x[0][i] );
+ }
+ return 0;
+}
+
+
diff --git a/lib/exp10m1.c b/lib/exp10m1.c
new file mode 100644
index 0000000..bf19615
--- /dev/null
+++ b/lib/exp10m1.c
@@ -0,0 +1,38 @@
+// Base-10 Exponential Minus 1
+//
+// exp10m1 - 10^x - 1
+//
+// y = 10^x - 1, computed as expm1(x * ln(10))
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_exp10m1_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_exp10m1_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_exp10m1(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_exp10m1_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = expm1( x[0][i] * log( 10.0 ) );
+ }
+ return 0;
+}
+
+
diff --git a/lib/exp2.c b/lib/exp2.c
new file mode 100644
index 0000000..b70bf1c
--- /dev/null
+++ b/lib/exp2.c
@@ -0,0 +1,40 @@
+// Base-2 Exponential
+//
+// exp2 - 2^x
+//
+// y = 2^x
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_exp2_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_exp2_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_exp2(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_exp2_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = exp2( x[0][i] );
+ }
+ return 0;
+}
+
+
diff --git a/lib/exp2m1.c b/lib/exp2m1.c
new file mode 100644
index 0000000..d0c6fb1
--- /dev/null
+++ b/lib/exp2m1.c
@@ -0,0 +1,38 @@
+// Base-2 Exponential Minus 1
+//
+// exp2m1 - 2^x - 1
+//
+// y = 2^x - 1, computed as expm1(x * ln(2))
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_exp2m1_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_exp2m1_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_exp2m1(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_exp2m1_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = expm1( x[0][i] * log( 2.0 ) );
+ }
+ return 0;
+}
+
+
diff --git a/lib/expit.c b/lib/expit.c
new file mode 100644
index 0000000..e58b528
--- /dev/null
+++ b/lib/expit.c
@@ -0,0 +1,40 @@
+// Inverse Logit (Expit)
+//
+// expit - logistic function
+//
+// y = 1 / (1 + e^(-x))
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_expit_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_expit_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_expit(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_expit_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = 1.0 / ( 1.0 + exp( -x[0][i] ) );
+ }
+ return 0;
+}
+
+
diff --git a/lib/expm1.c b/lib/expm1.c
new file mode 100644
index 0000000..c6cb7a2
--- /dev/null
+++ b/lib/expm1.c
@@ -0,0 +1,40 @@
+// Exponential Minus 1
+//
+// expm1 - e^x - 1
+//
+// y = e^x - 1, accurate for small x
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_expm1_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_expm1_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_expm1(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_expm1_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = expm1( x[0][i] );
+ }
+ return 0;
+}
+
+
diff --git a/lib/exponential.c b/lib/exponential.c
new file mode 100644
index 0000000..b3a6205
--- /dev/null
+++ b/lib/exponential.c
@@ -0,0 +1,49 @@
+// Exponential
+//
+// exponential - exp
+//
+// f( x ) = b^x
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 1
+//
+// c[0] = b
+
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ fp_t b;
+} ts_exponential_params_t;
+
+ts_parameter_t const ts_exponential_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_exponential_params_t) };
+
+
+uint_t ts_exponential_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_exponential(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_exponential_p );
+ if ( !c ) return -1;
+ fp_t b = ((ts_exponential_params_t *)c)->b;
+ if (b <= 0) return -1;
+
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = pow( b, x[0][i] );
+ }
+ return 0;
+}
+
+
diff --git a/lib/exponential_cdf.c b/lib/exponential_cdf.c
new file mode 100644
index 0000000..95c7ba8
--- /dev/null
+++ b/lib/exponential_cdf.c
@@ -0,0 +1,38 @@
+// Exponential CDF
+//
+// exponential_cdf - exponential cumulative distribution
+//
+// F(x) = 1 - e^(-x) for x >= 0, else 0
+//
+// Inputs: 1
+// Outputs: 1 (CDF value)
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_exponential_cdf_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_exponential_cdf_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_exponential_cdf(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_exponential_cdf_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = x[0][i] >= 0 ? 1.0 - exp( -x[0][i] ) : 0.0;
+ }
+ return 0;
+}
+
+
diff --git a/lib/exponential_natural.c b/lib/exponential_natural.c
new file mode 100644
index 0000000..61a217c
--- /dev/null
+++ b/lib/exponential_natural.c
@@ -0,0 +1,41 @@
+// Natural Exponential
+//
+// exponential_natural - expn
+//
+// f( x ) = e^x
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_exponential_natural_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_exponential_natural_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_exponential_natural(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_exponential_natural_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = exp( x[0][i] );
+ }
+ return 0;
+}
+
+
diff --git a/lib/exponential_pdf.c b/lib/exponential_pdf.c
new file mode 100644
index 0000000..ccb942a
--- /dev/null
+++ b/lib/exponential_pdf.c
@@ -0,0 +1,38 @@
+// Exponential PDF
+//
+// exponential_pdf - exponential probability density
+//
+// f(x) = e^(-x) for x >= 0, else 0
+//
+// Inputs: 1
+// Outputs: 1 (PDF value)
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_exponential_pdf_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_exponential_pdf_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_exponential_pdf(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_exponential_pdf_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = x[0][i] >= 0 ? exp( -x[0][i] ) : 0.0;
+ }
+ return 0;
+}
+
+
diff --git a/lib/factorial.c b/lib/factorial.c
new file mode 100644
index 0000000..8e64fa3
--- /dev/null
+++ b/lib/factorial.c
@@ -0,0 +1,47 @@
+// Factorial
+//
+// factorial - n!
+//
+// n! = 1 * 2 * ... * n, capped at 170!
+//
+// Inputs: 1 (n)
+// Outputs: 1 (n!)
+// Constants: 0
+
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_factorial_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_factorial_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_factorial(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_factorial_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ if (x[0][i] < 0) { y[0][i] = NAN; continue; }
+ fp_t result = 1.0;
+ uint_t k = (uint_t)x[0][i];
+ if (k > 170) k = 170;
+ for( uint_t j = 1; j <= k; ++j )
+ {
+ result *= (fp_t)j;
+ }
+ y[0][i] = result;
+ }
+ return 0;
+}
+
+
diff --git a/lib/factorial_int.c b/lib/factorial_int.c
new file mode 100644
index 0000000..51250aa
--- /dev/null
+++ b/lib/factorial_int.c
@@ -0,0 +1,45 @@
+// Integer Factorial
+//
+// factorial_int - n!
+//
+// n! = 1 * 2 * ... * n, capped at 170!
+//
+// Inputs: 1 (n)
+// Outputs: 1 (n!)
+// Constants: 0
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_factorial_int_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_factorial_int_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_factorial_int(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_factorial_int_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ if (x[0][i] < 0) { y[0][i] = NAN; continue; }
+ uint_t n_val = (uint_t)x[0][i];
+ if (n_val > 170) n_val = 170;
+ fp_t result = 1.0;
+ for( uint_t k = 2; k <= n_val; ++k )
+ {
+ result *= (fp_t)k;
+ }
+ y[0][i] = result;
+ }
+ return 0;
+}
+
+
diff --git a/lib/fdim_2input.c b/lib/fdim_2input.c
new file mode 100644
index 0000000..8d841ad
--- /dev/null
+++ b/lib/fdim_2input.c
@@ -0,0 +1,38 @@
+// Positive Difference
+//
+// fdim_2input - fdim(x, 0)
+//
+// max(x - 0, 0), positive part of x
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_fdim_2input_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_fdim_2input_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_fdim_2input(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_fdim_2input_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = fdim( x[0][i], 0.0 );
+ }
+ return 0;
+}
+
+
diff --git a/lib/fibonacci.c b/lib/fibonacci.c
new file mode 100644
index 0000000..43cd9a0
--- /dev/null
+++ b/lib/fibonacci.c
@@ -0,0 +1,49 @@
+// Fibonacci Number
+//
+// fibonacci - F(n)
+//
+// F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2)
+//
+// Inputs: 1 (n)
+// Outputs: 1 (F(n))
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_fibonacci_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_fibonacci_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_fibonacci(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_fibonacci_p );
+ (void)c;
+ for (uint_t i = 0; i < n; ++i)
+ {
+ if (x[0][i] < 0) { y[0][i] = 0; continue; }
+ uint_t k = (uint_t)floor(x[0][i]);
+ if (k == 0) { y[0][i] = 0; continue; }
+ if (k == 1) { y[0][i] = 1; continue; }
+ fp_t a = 0, b = 1, cv = 0;
+ for (uint_t j = 2; j <= k; ++j)
+ {
+ cv = a + b;
+ a = b;
+ b = cv;
+ }
+ y[0][i] = cv;
+ }
+ return 0;
+}
+
+
diff --git a/lib/fill_indicator.c b/lib/fill_indicator.c
new file mode 100644
index 0000000..9cb9cbc
--- /dev/null
+++ b/lib/fill_indicator.c
@@ -0,0 +1,63 @@
+// Fill Indicator
+//
+// fill_indicator - FI
+//
+// SMA of |C-O|/(H-L)*100, measures candle body fill ratio
+//
+// Inputs: 4 (OHLC)
+// Outputs: 1 (FI)
+// Constants: 1 (period)
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_fill_indicator_params_t;
+
+ts_parameter_t const ts_fill_indicator_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_fill_indicator_params_t) };
+
+uint_t ts_fill_indicator_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_fill_indicator_params_t *)c)->k > 0 ? ((ts_fill_indicator_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_fill_indicator(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_fill_indicator_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_fill_indicator_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ uint_t xi = 0;
+ uint_t yi = k - 1;
+
+ fp_t sum = 0;
+
+ for( ; xi < k; ++xi )
+ {
+ fp_t range = ts_h( x, xi ) - ts_l( x, xi );
+ if (range != 0)
+ sum += fabs( ts_c( x, xi ) - ts_o( x, xi ) ) / range * 100;
+ }
+
+ y[0][yi] = sum / k;
+ ++yi;
+
+ for( ; xi < n; ++xi, ++yi )
+ {
+ fp_t range = ts_h( x, xi ) - ts_l( x, xi );
+ fp_t fv = range != 0 ? fabs( ts_c( x, xi ) - ts_o( x, xi ) ) / range * 100 : 0;
+ fp_t range_old = ts_h( x, xi - k ) - ts_l( x, xi - k );
+ fp_t fo = range_old != 0 ? fabs( ts_c( x, xi - k ) - ts_o( x, xi - k ) ) / range_old * 100 : 0;
+ y[0][yi] = y[0][yi - 1] + ( fv - fo ) / k;
+ }
+ return 0;
+}
+
+
diff --git a/lib/fisher_transform.c b/lib/fisher_transform.c
new file mode 100644
index 0000000..2b416ba
--- /dev/null
+++ b/lib/fisher_transform.c
@@ -0,0 +1,78 @@
+// Fisher Transform
+//
+// fisher_transform - FT
+//
+// 0.5 * log(price_position / (1 - price_position)), normalized to range
+//
+// Inputs: 4 (OHLC)
+// Outputs: 2 (Transform, Trigger)
+// Constants: 1 (period)
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_fisher_transform_params_t;
+
+ts_parameter_t const ts_fisher_transform_p = { .n_inputs = 4, .n_outputs = 2, .n_constants = 1, .params_size = sizeof(ts_fisher_transform_params_t) };
+
+uint_t ts_fisher_transform_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_fisher_transform_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_fisher_transform(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_fisher_transform_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_fisher_transform_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for (uint_t i = k - 1; i < n; ++i)
+ {
+ /* Find high and low over window */
+ fp_t hh = x[1][i - k + 1];
+ fp_t ll = x[2][i - k + 1];
+ for (uint_t j = i - k + 2; j <= i; ++j)
+ {
+ if (x[1][j] > hh) hh = x[1][j];
+ if (x[2][j] < ll) ll = x[2][j];
+ }
+
+ fp_t range = hh - ll;
+ fp_t val = range != 0 ? (x[3][i] - ll) / range : 0.5;
+
+ /* Clamp to [0.01, 0.99] to avoid log(0) */
+ if (val < 0.01) val = 0.01;
+ if (val > 0.99) val = 0.99;
+
+ fp_t fisher = 0.5 * log(val / (1.0 - val));
+
+ /* Smooth with previous value (skip first output) */
+ uint_t out_i = i;
+ if (i > k - 1)
+ {
+ fisher = fisher + 0.5 * y[0][out_i - 1];
+ }
+ y[0][out_i] = fisher;
+
+ /* Trigger line = previous fisher */
+ if (out_i > 0)
+ {
+ y[1][out_i] = y[0][out_i - 1];
+ }
+ else
+ {
+ y[1][out_i] = 0;
+ }
+ }
+ return 0;
+}
+
+
diff --git a/lib/floor.c b/lib/floor.c
new file mode 100644
index 0000000..0a77858
--- /dev/null
+++ b/lib/floor.c
@@ -0,0 +1,41 @@
+// Floor
+//
+// floor - floor
+//
+// f( x ) = ⌊ x ⌋
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_floor_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_floor_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_floor(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_floor_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = floor( x[0][i] );
+ }
+ return 0;
+}
+
+
diff --git a/lib/fma.c b/lib/fma.c
new file mode 100644
index 0000000..795dc08
--- /dev/null
+++ b/lib/fma.c
@@ -0,0 +1,38 @@
+// Fused Multiply-Add
+//
+// fma - fused multiply-add
+//
+// x * y + z, computed in single precision
+//
+// Inputs: 3
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_fma_p = { .n_inputs = 3, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_fma_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_fma(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_fma_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = fma( x[0][i], x[1][i], x[2][i] );
+ }
+ return 0;
+}
+
+
diff --git a/lib/fmax.c b/lib/fmax.c
new file mode 100644
index 0000000..63eb5a7
--- /dev/null
+++ b/lib/fmax.c
@@ -0,0 +1,38 @@
+// Maximum
+//
+// fmax - max(x, y)
+//
+// Returns the maximum of two values
+//
+// Inputs: 2
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_fmax_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_fmax_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_fmax(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_fmax_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = fmax( x[0][i], x[1][i] );
+ }
+ return 0;
+}
+
+
diff --git a/lib/fmax_2input.c b/lib/fmax_2input.c
new file mode 100644
index 0000000..b29eee5
--- /dev/null
+++ b/lib/fmax_2input.c
@@ -0,0 +1,38 @@
+// Maximum with Zero
+//
+// fmax_2input - max(x, 0)
+//
+// Returns the maximum of x and 0
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_fmax_2input_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_fmax_2input_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_fmax_2input(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_fmax_2input_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = fmax( x[0][i], 0.0 );
+ }
+ return 0;
+}
+
+
diff --git a/lib/fmin.c b/lib/fmin.c
new file mode 100644
index 0000000..5820841
--- /dev/null
+++ b/lib/fmin.c
@@ -0,0 +1,38 @@
+// Minimum
+//
+// fmin - min(x, y)
+//
+// Returns the minimum of two values
+//
+// Inputs: 2
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_fmin_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_fmin_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_fmin(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_fmin_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = fmin( x[0][i], x[1][i] );
+ }
+ return 0;
+}
+
+
diff --git a/lib/fmin_2input.c b/lib/fmin_2input.c
new file mode 100644
index 0000000..ba4cd5c
--- /dev/null
+++ b/lib/fmin_2input.c
@@ -0,0 +1,38 @@
+// Minimum with Zero
+//
+// fmin_2input - min(x, 0)
+//
+// Returns the minimum of x and 0
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_fmin_2input_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_fmin_2input_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_fmin_2input(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_fmin_2input_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = fmin( x[0][i], 0.0 );
+ }
+ return 0;
+}
+
+
diff --git a/lib/fmod.c b/lib/fmod.c
new file mode 100644
index 0000000..75317bd
--- /dev/null
+++ b/lib/fmod.c
@@ -0,0 +1,38 @@
+// Floating Modulo
+//
+// fmod - floating-point remainder
+//
+// x mod y, remainder of x/y
+//
+// Inputs: 2
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_fmod_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_fmod_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_fmod(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_fmod_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = fmod( x[0][i], x[1][i] );
+ }
+ return 0;
+}
+
+
diff --git a/lib/fobv.c b/lib/fobv.c
new file mode 100644
index 0000000..7134cca
--- /dev/null
+++ b/lib/fobv.c
@@ -0,0 +1,43 @@
+// On-Balance Volume
+//
+// fobv - OBV
+//
+// Cumulative volume: add on up days, subtract on down days
+//
+// Inputs: 5 (OHLCV)
+// Outputs: 1 (OBV)
+// Constants: 0
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_fobv_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_fobv_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 1 );
+}
+
+int ts_fobv(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_fobv_p );
+ (void)c;
+ if (n > 0) y[0][0] = 0;
+ fp_t obv = 0;
+
+ for (uint_t i = 1; i < n; ++i)
+ {
+ if (x[3][i] > x[3][i - 1])
+ obv += x[4][i];
+ else if (x[3][i] < x[3][i - 1])
+ obv -= x[4][i];
+ y[0][i] = obv;
+ }
+ return 0;
+}
+
diff --git a/lib/fractal_chaos_bands.c b/lib/fractal_chaos_bands.c
new file mode 100644
index 0000000..57bbf00
--- /dev/null
+++ b/lib/fractal_chaos_bands.c
@@ -0,0 +1,74 @@
+// Fractal Chaos Bands
+//
+// fractal_chaos_bands
+//
+// Elder's Fractal Chaos Bands using fractal channels
+//
+// Upper/Lower: fractal-based channel boundaries
+//
+// Inputs: 4
+// Outputs: 2
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_fractal_chaos_bands_params_t;
+
+ts_parameter_t const ts_fractal_chaos_bands_p = { .n_inputs = 4, .n_outputs = 2, .n_constants = 1, .params_size = sizeof(ts_fractal_chaos_bands_params_t) };
+
+uint_t ts_fractal_chaos_bands_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_fractal_chaos_bands_params_t *)c)->k / 2 : 0 );
+}
+
+int ts_fractal_chaos_bands(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_fractal_chaos_bands_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_fractal_chaos_bands_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ fp_t alpha = 2.0 / (k + 1.0);
+ uint_t half_k = k / 2;
+
+ fp_t ema_upper = 0;
+ fp_t ema_lower = 0;
+ uint_t ema_count = 0;
+
+ for (uint_t i = half_k; i < n - half_k; ++i)
+ {
+ fp_t fh = x[1][i];
+ fp_t fl = x[2][i];
+ for (int_t j = -(int_t)half_k; j <= (int_t)half_k; ++j)
+ {
+ uint_t idx = (uint_t)((int_t)i + j);
+ if (x[1][idx] > fh) fh = x[1][idx];
+ if (x[2][idx] < fl) fl = x[2][idx];
+ }
+
+ if (ema_count == 0)
+ {
+ ema_upper = fh;
+ ema_lower = fl;
+ }
+ else
+ {
+ ema_upper = ema_upper + alpha * (fh - ema_upper);
+ ema_lower = ema_lower + alpha * (fl - ema_lower);
+ }
+ ema_count++;
+
+ y[0][i] = ema_upper;
+ y[1][i] = ema_lower;
+ }
+ return 0;
+}
+
diff --git a/lib/fractal_chaos_oscillator.c b/lib/fractal_chaos_oscillator.c
new file mode 100644
index 0000000..5c845d3
--- /dev/null
+++ b/lib/fractal_chaos_oscillator.c
@@ -0,0 +1,73 @@
+// Fractal Chaos Oscillator
+//
+// fractal_chaos_oscillator
+//
+// Fractal Chaos Oscillator
+//
+// Oscillator derived from fractal chaos bands
+//
+// Inputs: 4
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_fractal_chaos_oscillator_params_t;
+
+ts_parameter_t const ts_fractal_chaos_oscillator_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_fractal_chaos_oscillator_params_t) };
+
+uint_t ts_fractal_chaos_oscillator_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_fractal_chaos_oscillator_params_t *)c)->k / 2 : 0 );
+}
+
+int ts_fractal_chaos_oscillator(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_fractal_chaos_oscillator_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_fractal_chaos_oscillator_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ fp_t alpha = 2.0 / (k + 1.0);
+ fp_t ema_upper = 0;
+ fp_t ema_lower = 0;
+ uint_t half_k = k / 2;
+ uint_t init = 0;
+
+ for (uint_t i = half_k; i < n - half_k; ++i)
+ {
+ fp_t fh = x[1][i];
+ fp_t fl = x[2][i];
+ for (int_t j = -(int_t)half_k; j <= (int_t)half_k; ++j)
+ {
+ uint_t idx = (uint_t)((int_t)i + j);
+ if (x[1][idx] > fh) fh = x[1][idx];
+ if (x[2][idx] < fl) fl = x[2][idx];
+ }
+
+ if (!init)
+ {
+ ema_upper = fh;
+ ema_lower = fl;
+ init = 1;
+ }
+ else
+ {
+ ema_upper = ema_upper + alpha * (fh - ema_upper);
+ ema_lower = ema_lower + alpha * (fl - ema_lower);
+ }
+
+ fp_t denom = ema_upper + ema_lower;
+ y[0][i] = (denom != 0) ? (ema_upper - ema_lower) / denom * 100.0 : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/gamma_pdf.c b/lib/gamma_pdf.c
new file mode 100644
index 0000000..977f8b7
--- /dev/null
+++ b/lib/gamma_pdf.c
@@ -0,0 +1,48 @@
+// Gamma Pdf
+//
+// gamma_pdf
+//
+// Gamma probability density function
+//
+// f(x; α, β) = β^α * x^(α-1) * e^(-βx) / Γ(α)
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ fp_t shape;
+} ts_gamma_pdf_params_t;
+
+ts_parameter_t const ts_gamma_pdf_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_gamma_pdf_params_t) };
+
+uint_t ts_gamma_pdf_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_gamma_pdf(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_gamma_pdf_p );
+ if ( !c ) return -1;
+ fp_t shape = ((ts_gamma_pdf_params_t *)c)->shape;
+ if (shape <= 0.0) return -1;
+
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xi = x[0][i];
+ y[0][i] = xi > 0 ? pow( xi, shape - 1.0 ) * exp( -xi ) / tgamma( shape ) : 0.0;
+ }
+ return 0;
+}
+
+
diff --git a/lib/gator_oscillator.c b/lib/gator_oscillator.c
new file mode 100644
index 0000000..02bbc59
--- /dev/null
+++ b/lib/gator_oscillator.c
@@ -0,0 +1,64 @@
+// Gator Oscillator
+//
+// gator_oscillator
+//
+// Gator Oscillator from Alligator lines
+//
+// Histogram of Alligator Teeth-Lips and Jaw-Teeth
+//
+// Inputs: 1
+// Outputs: 2
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_gator_oscillator_p = { .n_inputs = 1, .n_outputs = 2, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_gator_oscillator_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 12 );
+}
+
+int ts_gator_oscillator(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_gator_oscillator_p );
+ (void)c;
+ /* Gator Oscillator uses Alligator lines:
+ * Lips = SMA(5), Teeth = SMA(8), Jaw = SMA(13)
+ * Upper = |Lips - Teeth|, Lower = |Jaw - Teeth|
+ * Offset 12 because Jaw (SMA13) starts at index 12
+ */
+ for (uint_t j = 0; j < n && j < 12; ++j) { y[0][j] = 0; y[1][j] = 0; }
+ for (uint_t i = 12; i < n; ++i)
+ {
+ /* SMA(5) - Lips */
+ fp_t lips = 0;
+ for (uint_t j = i - 4; j <= i; ++j) lips += x[0][j];
+ lips /= 5;
+
+ /* SMA(8) - Teeth */
+ fp_t teeth = 0;
+ for (uint_t j = i - 7; j <= i; ++j) teeth += x[0][j];
+ teeth /= 8;
+
+ /* SMA(13) - Jaw */
+ fp_t jaw = 0;
+ for (uint_t j = i - 12; j <= i; ++j) jaw += x[0][j];
+ jaw /= 13;
+
+ y[0][i] = fabs(lips - teeth);
+ y[1][i] = fabs(jaw - teeth);
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/gaussian_cdf.c b/lib/gaussian_cdf.c
new file mode 100644
index 0000000..2111aae
--- /dev/null
+++ b/lib/gaussian_cdf.c
@@ -0,0 +1,43 @@
+// Gaussian Cdf
+//
+// gaussian_cdf
+//
+// Gaussian cumulative distribution function
+//
+// F(x) = 0.5 * (1 + erf(x/√2))
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_gaussian_cdf_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_gaussian_cdf_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_gaussian_cdf(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_gaussian_cdf_p );
+ (void)c;
+ /* CDF = 0.5 * (1 + erf(x / sqrt(2))) */
+ for (uint_t i = 0; i < n; ++i)
+ {
+ y[0][i] = 0.5 * (1.0 + erf(x[0][i] / sqrt(2.0)));
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/gaussian_pdf.c b/lib/gaussian_pdf.c
new file mode 100644
index 0000000..01acc34
--- /dev/null
+++ b/lib/gaussian_pdf.c
@@ -0,0 +1,42 @@
+// Gaussian Pdf
+//
+// gaussian_pdf
+//
+// Gaussian probability density function
+//
+// f(x) = (1/√(2π)) * e^(-x²/2)
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_gaussian_pdf_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_gaussian_pdf_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_gaussian_pdf(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_gaussian_pdf_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = exp( -0.5 * x[0][i] * x[0][i] ) / sqrt( 2.0 * PI );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/gaussian_quantile.c b/lib/gaussian_quantile.c
new file mode 100644
index 0000000..337262c
--- /dev/null
+++ b/lib/gaussian_quantile.c
@@ -0,0 +1,94 @@
+// Gaussian Quantile
+//
+// gaussian_quantile
+//
+// Gaussian quantile function (inverse CDF)
+//
+// Q(p) = Φ^(-1)(p)
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_gaussian_quantile_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_gaussian_quantile_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_gaussian_quantile(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_gaussian_quantile_p );
+ (void)c;
+ /* Peter Acklam's approximation for inverse normal CDF */
+ /* Coefficients for rational approximation */
+ fp_t const a[] = {
+ -3.969683028665376e+01, 2.209460984245205e+02,
+ -2.759285104469687e+02, 1.383577518672690e+02,
+ -3.066479806614716e+01, 2.506628277459239e+00
+ };
+ fp_t const b[] = {
+ -5.447609879822406e+01, 1.615858368580409e+02,
+ -1.556989798598866e+02, 6.680131188771972e+01,
+ -1.328068155288572e+01
+ };
+ fp_t const cc[] = {
+ -7.784894002430993e-03, 3.223964580411365e-01,
+ -2.400758277161838e+00, -2.549732539343734e+00,
+ 4.374664141464968e+00, 2.938163982698783e+00
+ };
+ fp_t const d[] = {
+ 7.784695709041462e-03, 3.224671290700398e-01,
+ 2.445134137142996e+00, 3.754408661907416e+00
+ };
+
+ fp_t const p_low = 0.02425;
+ fp_t const p_high = 1.0 - p_low;
+
+ for (uint_t i = 0; i < n; ++i)
+ {
+ fp_t p = x[0][i];
+ if (p <= 0.0)
+ {
+ y[0][i] = -INFINITY;
+ continue;
+ }
+ if (p >= 1.0)
+ {
+ y[0][i] = INFINITY;
+ continue;
+ }
+
+ fp_t q, r;
+
+ if (p < p_low) {
+ q = sqrt(-2.0 * log(p));
+ y[0][i] = -(((((cc[0]*q + cc[1])*q + cc[2])*q + cc[3])*q + cc[4])*q + cc[5]) /
+ ((((d[0]*q + d[1])*q + d[2])*q + d[3])*q + 1.0);
+ } else if (p <= p_high) {
+ q = p - 0.5;
+ r = q * q;
+ y[0][i] = (((((a[0]*r + a[1])*r + a[2])*r + a[3])*r + a[4])*r + a[5])*q /
+ (((((b[0]*r + b[1])*r + b[2])*r + b[3])*r + b[4])*r + 1.0);
+ } else {
+ q = sqrt(-2.0 * log(1.0 - p));
+ y[0][i] = (((((cc[0]*q + cc[1])*q + cc[2])*q + cc[3])*q + cc[4])*q + cc[5]) /
+ ((((d[0]*q + d[1])*q + d[2])*q + d[3])*q + 1.0);
+ }
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/gcd.c b/lib/gcd.c
new file mode 100644
index 0000000..44068cf
--- /dev/null
+++ b/lib/gcd.c
@@ -0,0 +1,50 @@
+// Gcd
+//
+// gcd
+//
+// Greatest common divisor
+//
+// gcd(a, b) via Euclidean algorithm
+//
+// Inputs: 2
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_gcd_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_gcd_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_gcd(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_gcd_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ long a = (long)fabs(x[0][i]);
+ long b = (long)fabs(x[1][i]);
+ while( b )
+ {
+ long t = b;
+ b = a % b;
+ a = t;
+ }
+ y[0][i] = (fp_t)a;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/gopalakrishnan_range_index.c b/lib/gopalakrishnan_range_index.c
new file mode 100644
index 0000000..80da0b4
--- /dev/null
+++ b/lib/gopalakrishnan_range_index.c
@@ -0,0 +1,64 @@
+// Gopalakrishnan Range Index
+//
+// gopalakrishnan_range_index
+//
+// Gopalakrishnan Range Index
+//
+// Measures price range relative to ATR
+//
+// Inputs: 4
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_gopalakrishnan_range_index_params_t;
+
+ts_parameter_t const ts_gopalakrishnan_range_index_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_gopalakrishnan_range_index_params_t) };
+
+uint_t ts_gopalakrishnan_range_index_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_gopalakrishnan_range_index_params_t *)c)->k : 0 );
+}
+
+int ts_gopalakrishnan_range_index(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_gopalakrishnan_range_index_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_gopalakrishnan_range_index_params_t *)c)->k;
+ if (k == 0 || n <= k) return -1;
+
+ fp_t sum_abs = 0;
+ fp_t sum_range = 0;
+
+ for (uint_t j = 1; j <= k; ++j)
+ {
+ sum_abs += fabs( ts_c( x, j ) - ts_c( x, j - 1 ) );
+ sum_range += ts_h( x, j ) - ts_l( x, j );
+ }
+
+ y[0][k] = sum_range != 0 ? sum_abs / sum_range : 0;
+
+ for (uint_t i = k + 1; i < n; ++i)
+ {
+ uint_t dj = i - k;
+ sum_abs -= fabs( ts_c( x, dj ) - ts_c( x, dj - 1 ) );
+ sum_range -= ts_h( x, dj ) - ts_l( x, dj );
+ sum_abs += fabs( ts_c( x, i ) - ts_c( x, i - 1 ) );
+ sum_range += ts_h( x, i ) - ts_l( x, i );
+ y[0][i] = sum_range != 0 ? sum_abs / sum_range : 0;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/hard_sigmoid.c b/lib/hard_sigmoid.c
new file mode 100644
index 0000000..f54d4af
--- /dev/null
+++ b/lib/hard_sigmoid.c
@@ -0,0 +1,47 @@
+// Hard Sigmoid
+//
+// hard_sigmoid - Hard Sigmoid activation
+//
+// y[t] = x[t] <= -3 ? 0 : x[t] >= 3 ? 1 : x[t]/6 + 0.5
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_hard_sigmoid_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_hard_sigmoid_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_hard_sigmoid(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_hard_sigmoid_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xi = x[0][i];
+ if ( xi <= -3 )
+ y[0][i] = 0;
+ else if ( xi >= 3 )
+ y[0][i] = 1;
+ else
+ y[0][i] = xi / 6 + 0.5;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/hard_swish.c b/lib/hard_swish.c
new file mode 100644
index 0000000..458ba8d
--- /dev/null
+++ b/lib/hard_swish.c
@@ -0,0 +1,47 @@
+// Hard Swish
+//
+// hard_swish - Hard Swish activation
+//
+// y[t] = x[t] <= -3 ? 0 : x[t] >= 3 ? x[t] : x[t] * ( x[t] + 3 ) / 6
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_hard_swish_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_hard_swish_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_hard_swish(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_hard_swish_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xi = x[0][i];
+ if ( xi <= -3 )
+ y[0][i] = 0;
+ else if ( xi >= 3 )
+ y[0][i] = xi;
+ else
+ y[0][i] = xi * ( xi + 3 ) / 6;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/hard_tanh.c b/lib/hard_tanh.c
new file mode 100644
index 0000000..36301d3
--- /dev/null
+++ b/lib/hard_tanh.c
@@ -0,0 +1,48 @@
+// Hard Tanh
+//
+// hard_tanh - Hard Tanh activation
+//
+// y[t] = clamp( x[t], -1, 1 )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_hard_tanh_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_hard_tanh_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_hard_tanh(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_hard_tanh_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xi = x[0][i];
+ if ( xi < -1 )
+ y[0][i] = -1;
+ else if ( xi > 1 )
+ y[0][i] = 1;
+ else
+ y[0][i] = xi;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/harmonic.c b/lib/harmonic.c
new file mode 100644
index 0000000..2c69ee8
--- /dev/null
+++ b/lib/harmonic.c
@@ -0,0 +1,50 @@
+// Harmonic
+//
+// harmonic
+//
+// Harmonic mean
+//
+// H = n / Σ(1/x_i)
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_harmonic_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_harmonic_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_harmonic(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_harmonic_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ if (x[0][i] < 0) { y[0][i] = NAN; continue; }
+ uint_t n_val = (uint_t)x[0][i];
+ if (n_val > 10000000) n_val = 10000000;
+ fp_t sum = 0.0;
+ for( uint_t k = 1; k <= n_val; ++k )
+ {
+ sum += 1.0 / (fp_t)k;
+ }
+ y[0][i] = sum;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/hermite.c b/lib/hermite.c
new file mode 100644
index 0000000..0418c6b
--- /dev/null
+++ b/lib/hermite.c
@@ -0,0 +1,64 @@
+// Hermite
+//
+// hermite
+//
+// Hermite polynomial
+//
+// H_n(x) via recurrence relation
+//
+// Inputs: 2
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_hermite_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_hermite_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_hermite(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_hermite_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xv = x[0][i];
+ if (x[1][i] < 0 || x[1][i] > 10000) { y[0][i] = 0; continue; }
+ uint_t n_val = (uint_t)x[1][i];
+ if( n_val == 0 )
+ {
+ y[0][i] = 1.0;
+ }
+ else if( n_val == 1 )
+ {
+ y[0][i] = 2.0 * xv;
+ }
+ else
+ {
+ fp_t h0 = 1.0;
+ fp_t h1 = 2.0 * xv;
+ for( uint_t k = 2; k <= n_val; ++k )
+ {
+ fp_t h2 = 2.0 * xv * h1 - 2.0 * (fp_t)(k - 1) * h0;
+ h0 = h1;
+ h1 = h2;
+ }
+ y[0][i] = h1;
+ }
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/high_low_bands.c b/lib/high_low_bands.c
new file mode 100644
index 0000000..544796f
--- /dev/null
+++ b/lib/high_low_bands.c
@@ -0,0 +1,71 @@
+// High Low Bands
+//
+// high_low_bands
+//
+// High-Low Bands
+//
+// Upper: highest high, Lower: lowest low over period
+//
+// Inputs: 4
+// Outputs: 2
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_high_low_bands_params_t;
+
+ts_parameter_t const ts_high_low_bands_p = { .n_inputs = 4, .n_outputs = 2, .n_constants = 1, .params_size = sizeof(ts_high_low_bands_params_t) };
+
+uint_t ts_high_low_bands_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? 2 * ((ts_high_low_bands_params_t *)c)->k - 2 : 0 );
+}
+
+int ts_high_low_bands(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_high_low_bands_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_high_low_bands_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for( uint_t i = 2 * k - 2; i < n; ++i )
+ {
+ y[0][i] = 0;
+ y[1][i] = 0;
+ for( uint_t j = i - 2 * k + 2; j <= i - k + 1; ++j )
+ {
+ uint_t start = (j >= k - 1) ? j - k + 1 : 0;
+
+ fp_t hh = x[1][start];
+ for( uint_t m = start + 1; m <= j; ++m )
+ {
+ if( x[1][m] > hh )
+ hh = x[1][m];
+ }
+ y[0][i] += hh;
+
+ fp_t ll = x[2][start];
+ for( uint_t m = start + 1; m <= j; ++m )
+ {
+ if( x[2][m] < ll )
+ ll = x[2][m];
+ }
+ y[1][i] += ll;
+ }
+ y[0][i] /= k;
+ y[1][i] /= k;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/high_minus_low.c b/lib/high_minus_low.c
new file mode 100644
index 0000000..b38fcbb
--- /dev/null
+++ b/lib/high_minus_low.c
@@ -0,0 +1,42 @@
+// High Minus Low
+//
+// high_minus_low
+//
+// High minus Low
+//
+// H - L
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_high_minus_low_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_high_minus_low_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_high_minus_low(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_high_minus_low_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = x[1][i] - x[2][i];
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/highest_high.c b/lib/highest_high.c
new file mode 100644
index 0000000..d3ac753
--- /dev/null
+++ b/lib/highest_high.c
@@ -0,0 +1,67 @@
+// Highest High
+//
+// highest_high
+//
+// Highest high over rolling window
+//
+// HH = max(H[t-k+1..t])
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_highest_high_params_t;
+
+ts_parameter_t const ts_highest_high_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_highest_high_params_t) };
+
+uint_t ts_highest_high_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_highest_high_params_t *)c)->k > 0 ? ((ts_highest_high_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_highest_high(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_highest_high_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_highest_high_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ fp_t max_val = ts_h( x, 0 );
+ for( uint_t j = 1; j < k; ++j )
+ {
+ fp_t val = ts_h( x, j );
+ if( val > max_val )
+ max_val = val;
+ }
+ y[0][k - 1] = max_val;
+
+ for( uint_t i = k; i < n; ++i )
+ {
+ fp_t new_val = ts_h( x, i );
+ fp_t old_val = ts_h( x, i - k );
+ if ( new_val > max_val ) max_val = new_val;
+ if ( old_val == max_val )
+ {
+ max_val = ts_h( x, i - k + 1 );
+ for( uint_t j = i - k + 2; j <= i; ++j )
+ {
+ fp_t val = ts_h( x, j );
+ if( val > max_val ) max_val = val;
+ }
+ }
+ y[0][i] = max_val;
+ }
+ return 0;
+}
+
diff --git a/lib/historical_volatility.c b/lib/historical_volatility.c
new file mode 100644
index 0000000..cde0d91
--- /dev/null
+++ b/lib/historical_volatility.c
@@ -0,0 +1,62 @@
+// Historical Volatility
+//
+// historical_volatility
+//
+// Historical volatility of log returns
+//
+// stddev(log(C[t]/C[t-1])) over period
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_historical_volatility_params_t;
+
+ts_parameter_t const ts_historical_volatility_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_historical_volatility_params_t) };
+
+uint_t ts_historical_volatility_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_historical_volatility_params_t *)c)->k > 0 ? ((ts_historical_volatility_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_historical_volatility(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_historical_volatility_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_historical_volatility_params_t *)c)->k;
+ if (k == 0 || n < k + 1) return -1;
+
+ for ( uint_t d = k - 1; d < n - 1; ++d )
+ {
+ fp_t sum = 0;
+ fp_t sum2 = 0;
+ for ( uint_t j = d - k + 1; j <= d; ++j )
+ {
+ fp_t lr = 0;
+ if ( x[0][j] > 0 && x[0][j + 1] > 0 )
+ lr = log( x[0][j + 1] / x[0][j] );
+ sum += lr;
+ sum2 += lr * lr;
+ }
+ fp_t mean = sum / ( fp_t )k;
+ fp_t variance = sum2 / ( fp_t )k - mean * mean;
+ y[0][d] = variance > 0 ? sqrt( variance ) * sqrt( 252.0 ) : 0;
+ }
+
+ y[0][n - 1] = 0;
+ return 0;
+}
+
+
+
diff --git a/lib/hull_ma.c b/lib/hull_ma.c
new file mode 100644
index 0000000..e412a6f
--- /dev/null
+++ b/lib/hull_ma.c
@@ -0,0 +1,89 @@
+// Hull Ma
+//
+// hull_ma
+//
+// Hull Moving Average
+//
+// HMA = WMA(2*WMA(n/2) - WMA(n), √n)
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include <stdlib.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_hull_ma_params_t;
+
+ts_parameter_t const ts_hull_ma_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_hull_ma_params_t) };
+
+uint_t ts_hull_ma_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_hull_ma_params_t *)c)->k + (uint_t)sqrt((fp_t)((ts_hull_ma_params_t *)c)->k) - 2 : 0 );
+}
+
+int ts_hull_ma(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_hull_ma_p );
+ if ( !c ) return -1;
+ TS_CHECK_INPUT(x, x_n, n);
+ uint_t k = ((ts_hull_ma_params_t *)c)->k;
+ if (k < 2) return -1;
+
+ uint_t half_k = k / 2;
+ uint_t sqrt_k = (uint_t)sqrt((fp_t)k);
+ if (sqrt_k < 1) sqrt_k = 1;
+
+ fp_t *wma_half = calloc( n, sizeof( fp_t ) );
+ fp_t *wma_full = calloc( n, sizeof( fp_t ) );
+ if ( !wma_half || !wma_full ) { free( wma_half ); free( wma_full ); return -1; }
+
+ /* WMA(n/2) */
+ fp_t wt_half = (fp_t)half_k * ((fp_t)half_k + 1.0) / 2.0;
+ for ( uint_t i = half_k - 1; i < n; ++i )
+ {
+ fp_t s = 0;
+ for ( uint_t j = 0; j < half_k; ++j )
+ s += x[0][i - j] * (fp_t)(half_k - j);
+ wma_half[i] = s / wt_half;
+ }
+
+ /* WMA(n) */
+ fp_t wt_full = (fp_t)k * ((fp_t)k + 1.0) / 2.0;
+ for ( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t s = 0;
+ for ( uint_t j = 0; j < k; ++j )
+ s += x[0][i - j] * (fp_t)(k - j);
+ wma_full[i] = s / wt_full;
+ }
+
+ /* HMA = WMA(sqrt(n), 2*WMA(n/2) - WMA(n)) */
+ fp_t wt_sqrt = (fp_t)sqrt_k * ((fp_t)sqrt_k + 1.0) / 2.0;
+ uint_t offset = k + sqrt_k - 2;
+ for (uint_t j = 0; j < n && j < offset; ++j) y[0][j] = NAN;
+ for ( uint_t i = offset; i < n; ++i )
+ {
+ fp_t s = 0;
+ for ( uint_t j = 0; j < sqrt_k; ++j )
+ {
+ fp_t diff = 2.0 * wma_half[i - j] - wma_full[i - j];
+ s += diff * (fp_t)(sqrt_k - j);
+ }
+ y[0][i] = s / wt_sqrt;
+ }
+
+ free( wma_half );
+ free( wma_full );
+ return 0;
+}
+
diff --git a/lib/hurst_exponent.c b/lib/hurst_exponent.c
new file mode 100644
index 0000000..aa58036
--- /dev/null
+++ b/lib/hurst_exponent.c
@@ -0,0 +1,122 @@
+// Hurst Exponent
+//
+// hurst_exponent
+//
+// Hurst exponent via R/S analysis
+//
+// H = slope of linear regression of log(R/S) vs log(n)
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_hurst_exponent_params_t;
+
+ts_parameter_t const ts_hurst_exponent_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_hurst_exponent_params_t) };
+
+uint_t ts_hurst_exponent_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_hurst_exponent(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_hurst_exponent_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_hurst_exponent_params_t *)c)->k;
+ if (k == 0 || k > n) return -1;
+
+ for ( uint_t i = 0; i < n; ++i )
+ {
+ if (i + 1 < k) {
+ y[0][i] = 0;
+ continue;
+ }
+
+ uint_t data_len = i + 1;
+ uint_t num_lags = 0;
+ fp_t sum_log_s = 0;
+ fp_t sum_log_rs = 0;
+ fp_t sum_log_s2 = 0;
+ fp_t sum_log_s_log_rs = 0;
+
+ for ( uint_t s = 2; s <= k && s <= data_len / 2; ++s )
+ {
+ uint_t ns = data_len / s;
+ if (ns == 0) continue;
+
+ fp_t total_log_rs = 0;
+ uint_t valid_segs = 0;
+
+ for ( uint_t seg = 0; seg < ns; ++seg )
+ {
+ uint_t start = seg * s;
+ uint_t end = start + s;
+ if (end > data_len) break;
+
+ fp_t mean = 0;
+ for ( uint_t p = start; p < end; ++p )
+ mean += x[0][p];
+ mean /= (fp_t)s;
+
+ fp_t cumsum = 0;
+ fp_t max_dev = 0, min_dev = 0;
+ for ( uint_t p = start; p < end; ++p )
+ {
+ cumsum += x[0][p] - mean;
+ if (cumsum > max_dev) max_dev = cumsum;
+ if (cumsum < min_dev) min_dev = cumsum;
+ }
+
+ fp_t r = max_dev - min_dev;
+ fp_t std = 0;
+ for ( uint_t p = start; p < end; ++p )
+ {
+ fp_t d = x[0][p] - mean;
+ std += d * d;
+ }
+ std = sqrt(std / (fp_t)s);
+
+ if (std > 0 && r > 0) {
+ total_log_rs += log(r / std);
+ valid_segs++;
+ }
+ }
+
+ if (valid_segs > 0) {
+ fp_t log_s = log((fp_t)s);
+ fp_t mean_log_rs = total_log_rs / (fp_t)valid_segs;
+ sum_log_s += log_s;
+ sum_log_rs += mean_log_rs;
+ sum_log_s2 += log_s * log_s;
+ sum_log_s_log_rs += log_s * mean_log_rs;
+ num_lags++;
+ }
+ }
+
+ if (num_lags >= 2) {
+ fp_t denom = (fp_t)num_lags * sum_log_s2 - sum_log_s * sum_log_s;
+ if (fabs(denom) > 1e-300) {
+ fp_t h = ((fp_t)num_lags * sum_log_s_log_rs - sum_log_s * sum_log_rs) / denom;
+ y[0][i] = h;
+ } else {
+ y[0][i] = 0;
+ }
+ } else {
+ y[0][i] = 0;
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/hypot.c b/lib/hypot.c
new file mode 100644
index 0000000..42fafa7
--- /dev/null
+++ b/lib/hypot.c
@@ -0,0 +1,42 @@
+// Hypot
+//
+// hypot
+//
+// Hypotenuse
+//
+// √(x² + y²)
+//
+// Inputs: 2
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_hypot_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_hypot_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_hypot(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_hypot_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = hypot( x[0][i], x[1][i] );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/ichimoku.c b/lib/ichimoku.c
new file mode 100644
index 0000000..76f85e6
--- /dev/null
+++ b/lib/ichimoku.c
@@ -0,0 +1,113 @@
+// Ichimoku
+//
+// ichimoku
+//
+// Ichimoku Cloud
+//
+// Tenkan, Kijun, Senkou Span A/B, Chikou
+//
+// Inputs: 4
+// Outputs: 5
+// Constants: 3 (tenkan, kijun, senkou)
+
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ uint_t tenkan;
+ uint_t kijun;
+ uint_t senkou;
+} ts_ichimoku_params_t;
+
+ts_parameter_t const ts_ichimoku_p = { .n_inputs = 4, .n_outputs = 5, .n_constants = 3, .params_size = sizeof(ts_ichimoku_params_t) };
+
+uint_t ts_ichimoku_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 52 );
+}
+
+int ts_ichimoku(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_ichimoku_p );
+ uint_t tenkan = 9;
+ uint_t kijun = 26;
+ uint_t senkou = 52;
+
+ if ( c )
+ {
+ ts_ichimoku_params_t *p = (ts_ichimoku_params_t *)c;
+ if ( p->tenkan > 0 ) tenkan = p->tenkan;
+ if ( p->kijun > 0 ) kijun = p->kijun;
+ if ( p->senkou > 0 ) senkou = p->senkou;
+ }
+
+ if ( tenkan == 0 || kijun == 0 || senkou == 0 ) return -1;
+
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = 0;
+ y[1][i] = 0;
+ y[2][i] = 0;
+ y[3][i] = 0;
+ y[4][i] = 0;
+ }
+
+ for( uint_t i = tenkan - 1; i < n; ++i )
+ {
+ fp_t hh = ts_h( x, i - tenkan + 1 );
+ fp_t ll = ts_l( x, i - tenkan + 1 );
+ for( uint_t j = i - tenkan + 1; j <= i; ++j )
+ {
+ if( ts_h( x, j ) > hh ) hh = ts_h( x, j );
+ if( ts_l( x, j ) < ll ) ll = ts_l( x, j );
+ }
+ y[0][i] = ( hh + ll ) / 2.0;
+ }
+
+ for( uint_t i = kijun - 1; i < n; ++i )
+ {
+ fp_t hh = ts_h( x, i - kijun + 1 );
+ fp_t ll = ts_l( x, i - kijun + 1 );
+ for( uint_t j = i - kijun + 1; j <= i; ++j )
+ {
+ if( ts_h( x, j ) > hh ) hh = ts_h( x, j );
+ if( ts_l( x, j ) < ll ) ll = ts_l( x, j );
+ }
+ y[1][i] = ( hh + ll ) / 2.0;
+ }
+
+ for( uint_t i = kijun - 1; i < n; ++i )
+ {
+ uint_t idx = i + kijun;
+ if( idx < n )
+ y[2][idx] = ( y[0][i] + y[1][i] ) / 2.0;
+ }
+
+ for( uint_t i = senkou - 1; i < n; ++i )
+ {
+ fp_t hh = ts_h( x, i - senkou + 1 );
+ fp_t ll = ts_l( x, i - senkou + 1 );
+ for( uint_t j = i - senkou + 1; j <= i; ++j )
+ {
+ if( ts_h( x, j ) > hh ) hh = ts_h( x, j );
+ if( ts_l( x, j ) < ll ) ll = ts_l( x, j );
+ }
+ uint_t idx = i + kijun;
+ if( idx < n )
+ y[3][idx] = ( hh + ll ) / 2.0;
+ }
+
+ for( uint_t i = kijun; i < n; ++i )
+ {
+ y[4][i] = ts_c( x, i - kijun );
+ }
+ return 0;
+}
+
diff --git a/lib/ilogb.c b/lib/ilogb.c
new file mode 100644
index 0000000..82cda70
--- /dev/null
+++ b/lib/ilogb.c
@@ -0,0 +1,42 @@
+// Ilogb
+//
+// ilogb
+//
+// Integer logarithm base
+//
+// exponent of the floating-point representation
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_ilogb_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_ilogb_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_ilogb(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_ilogb_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = (fp_t)ilogb( x[0][i] );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/intraday_momentum_index.c b/lib/intraday_momentum_index.c
new file mode 100644
index 0000000..79209bd
--- /dev/null
+++ b/lib/intraday_momentum_index.c
@@ -0,0 +1,57 @@
+// Intraday Momentum Index
+//
+// intraday_momentum_index
+//
+// Intraday Momentum Index
+//
+// SMA of up moves / total moves
+//
+// Inputs: 4
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_intraday_momentum_index_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_intraday_momentum_index_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 3 );
+}
+
+int ts_intraday_momentum_index(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_intraday_momentum_index_p );
+ (void)c;
+ uint_t k = 3;
+
+ for (uint_t i = 0; i < k; ++i) y[0][i] = 0;
+
+ for( uint_t i = k; i < n; ++i )
+ {
+ fp_t sum_up = 0;
+ fp_t sum_down = 0;
+
+ for( uint_t j = 0; j < k; ++j )
+ {
+ fp_t diff = ts_o( x, i - k + 1 + j ) - ts_c( x, i - k + j );
+ if( diff > 0 )
+ sum_up += diff;
+ else if( diff < 0 )
+ sum_down += (-diff);
+ }
+
+ y[0][i] = (sum_up + sum_down) == 0 ? 0 : sum_up / (sum_up + sum_down);
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/is_prime.c b/lib/is_prime.c
new file mode 100644
index 0000000..119c9a4
--- /dev/null
+++ b/lib/is_prime.c
@@ -0,0 +1,69 @@
+// Is Prime
+//
+// is_prime
+//
+// Prime number test
+//
+// 1 if prime, 0 otherwise
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_is_prime_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_is_prime_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_is_prime(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_is_prime_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t v = x[0][i];
+ if (v > 1e15 || v < -1e15) { y[0][i] = 0.0; continue; }
+ long n_val = (long)v;
+ if( n_val < 2 )
+ {
+ y[0][i] = 0.0;
+ continue;
+ }
+ if( n_val == 2 )
+ {
+ y[0][i] = 1.0;
+ continue;
+ }
+ if( n_val % 2 == 0 )
+ {
+ y[0][i] = 0.0;
+ continue;
+ }
+ int prime = 1;
+ for( long d = 3; d <= n_val / d; d += 2 )
+ {
+ if( n_val % d == 0 )
+ {
+ prime = 0;
+ break;
+ }
+ }
+ y[0][i] = prime ? 1.0 : 0.0;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/jaccard_similarity.c b/lib/jaccard_similarity.c
new file mode 100644
index 0000000..b2ffee6
--- /dev/null
+++ b/lib/jaccard_similarity.c
@@ -0,0 +1,45 @@
+// Jaccard Similarity
+//
+// jaccard_similarity
+//
+// Jaccard similarity coefficient
+//
+//
+//
+// Inputs: 2
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_jaccard_similarity_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_jaccard_similarity_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_jaccard_similarity(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_jaccard_similarity_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t const a = x[0][i];
+ fp_t const b = x[1][i];
+ fp_t const m = fmax( a, b );
+ y[0][i] = m == 0.0 ? 0.0 : ( a + b ) / ( 2.0 * m );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/kalman_filter.c b/lib/kalman_filter.c
new file mode 100644
index 0000000..f7adf6f
--- /dev/null
+++ b/lib/kalman_filter.c
@@ -0,0 +1,63 @@
+// Kalman Filter
+//
+// kalman_filter
+//
+// Kalman Filter
+//
+// Optimal state estimation via predict-update cycle
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 2
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ fp_t Q;
+ fp_t R;
+} ts_kalman_filter_params_t;
+
+ts_parameter_t const ts_kalman_filter_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 2, .params_size = sizeof(ts_kalman_filter_params_t) };
+
+uint_t ts_kalman_filter_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_kalman_filter(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_kalman_filter_p );
+ if ( !c ) return -1;
+ TS_CHECK_INPUT(x, x_n, n);
+ fp_t Q = ((ts_kalman_filter_params_t *)c)->Q;
+ fp_t R = ((ts_kalman_filter_params_t *)c)->R;
+
+ if (Q < 0 || R < 0) return -1;
+ if (isnan(Q) || isnan(R)) return -1;
+ if (n == 0) return -1;
+ fp_t x_est = x[0][0];
+ /* NOTE: P=1.0 is a reasonable default for normalized data.
+ * For price data in the range of 100-3000, consider initializing
+ * P to the variance of the first few samples. */
+ fp_t P = 1.0;
+
+ y[0][0] = x_est;
+ for ( uint_t i = 1; i < n; ++i )
+ {
+ P = P + Q;
+ fp_t denom = P + R;
+ fp_t K = denom != 0 ? P / denom : 0;
+ x_est = x_est + K * (x[0][i] - x_est);
+ P = (1 - K) * P;
+ y[0][i] = x_est;
+ }
+ return 0;
+}
+
diff --git a/lib/kaufman_adaptive_ma.c b/lib/kaufman_adaptive_ma.c
new file mode 100644
index 0000000..f56c6a1
--- /dev/null
+++ b/lib/kaufman_adaptive_ma.c
@@ -0,0 +1,61 @@
+// Kaufman Adaptive Ma
+//
+// kaufman_adaptive_ma
+//
+// Kaufman Adaptive Moving Average
+//
+// AMA based on efficiency ratio
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_kaufman_adaptive_ma_params_t;
+
+ts_parameter_t const ts_kaufman_adaptive_ma_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_kaufman_adaptive_ma_params_t) };
+
+uint_t ts_kaufman_adaptive_ma_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_kaufman_adaptive_ma_params_t *)c)->k : 0 );
+}
+
+int ts_kaufman_adaptive_ma(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_kaufman_adaptive_ma_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_kaufman_adaptive_ma_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ fp_t ama = x[0][0];
+ fp_t sm = 2.0 / ((fp_t)k + 1.0);
+ fp_t sn = 2.0 / (sqrt((fp_t)k) + 1.0);
+
+ for (uint_t i = k; i < n; ++i)
+ {
+ fp_t price_change = fabs(x[0][i] - x[0][i - k]);
+ fp_t noise = 0;
+ for (uint_t j = i - k + 1; j <= i; ++j)
+ noise += fabs(x[0][j] - x[0][j - 1]);
+
+ fp_t er = noise != 0 ? price_change / noise : 0;
+ fp_t er_sq = er * er;
+ fp_t sc = er_sq * (sn - sm) + sm;
+ sc = sc * sc;
+
+ ama = ama + sc * (x[0][i] - ama);
+ y[0][i] = ama;
+ }
+ return 0;
+}
+
diff --git a/lib/keltner_channel.c b/lib/keltner_channel.c
new file mode 100644
index 0000000..259be03
--- /dev/null
+++ b/lib/keltner_channel.c
@@ -0,0 +1,78 @@
+// Keltner Channel
+//
+// keltner_channel
+//
+// Keltner Channel
+//
+// Middle: EMA, Upper/Lower: EMA ± mult*ATR
+//
+// Inputs: 4
+// Outputs: 3
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_keltner_channel_params_t;
+
+ts_parameter_t const ts_keltner_channel_p = { .n_inputs = 4, .n_outputs = 3, .n_constants = 1, .params_size = sizeof(ts_keltner_channel_params_t) };
+
+uint_t ts_keltner_channel_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_keltner_channel_params_t *)c)->k : 0 );
+}
+
+int ts_keltner_channel(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_keltner_channel_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_keltner_channel_params_t *)c)->k;
+ if (k == 0 || n <= k) return -1;
+
+ fp_t a = 2.0 / ((fp_t)k + 1.0);
+
+ fp_t ema_tp = ( ts_h( x, 0 ) + ts_l( x, 0 ) + ts_c( x, 0 ) ) / 3.0;
+
+ fp_t tr = ts_h( x, 1 ) - ts_l( x, 1 );
+ fp_t tr2 = fabs( ts_h( x, 1 ) - ts_c( x, 0 ) );
+ fp_t tr3 = fabs( ts_l( x, 1 ) - ts_c( x, 0 ) );
+ if( tr2 > tr ) tr = tr2;
+ if( tr3 > tr ) tr = tr3;
+ fp_t atr = tr;
+
+ for (uint_t i = 0; i < k; ++i) { y[0][i] = 0; y[1][i] = 0; y[2][i] = 0; }
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ fp_t tp = ( ts_h( x, i ) + ts_l( x, i ) + ts_c( x, i ) ) / 3.0;
+ ema_tp = ema_tp + a * ( tp - ema_tp );
+
+ tr = ts_h( x, i ) - ts_l( x, i );
+ tr2 = fabs( ts_h( x, i ) - ts_c( x, i - 1 ) );
+ tr3 = fabs( ts_l( x, i ) - ts_c( x, i - 1 ) );
+ if( tr2 > tr ) tr = tr2;
+ if( tr3 > tr ) tr = tr3;
+
+ if( i >= 2 )
+ atr = atr + (tr - atr) / k;
+
+ if( i >= k )
+ {
+ y[0][i] = ema_tp;
+ y[1][i] = ema_tp + 2.0 * atr;
+ y[2][i] = ema_tp - 2.0 * atr;
+ }
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/kvo.c b/lib/kvo.c
new file mode 100644
index 0000000..229fad8
--- /dev/null
+++ b/lib/kvo.c
@@ -0,0 +1,105 @@
+// Kvo
+//
+// kvo
+//
+// Klinger Volume Oscillator
+//
+// Volume-weighted price direction oscillator
+//
+// Inputs: 5
+// Outputs: 2
+// Constants: 2
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k1;
+ fp_t f0;
+} ts_kvo_params_t;
+
+ts_parameter_t const ts_kvo_p = { .n_inputs = 5, .n_outputs = 2, .n_constants = 2, .params_size = sizeof(ts_kvo_params_t) };
+
+uint_t ts_kvo_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_kvo_params_t *)c)->k1 > 10 ? ((ts_kvo_params_t *)c)->k1 : 10 : 0 );
+}
+
+int ts_kvo(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_kvo_p );
+ if ( !c ) return -1;
+ uint_t k1 = ((ts_kvo_params_t *)c)->k1;
+ fp_t f0_raw = ((ts_kvo_params_t *)c)->f0;
+ if (isnan(f0_raw) || isinf(f0_raw) || f0_raw < 0) return -1;
+ uint_t k10 = (uint_t)f0_raw;
+ if (k1 == 0) return -1;
+ k10 = k1 > 10 ? k1 : 10;
+
+ fp_t ema_short_dvm = 0;
+ fp_t ema_long_absdvm = 0;
+ fp_t ema_signal = 0;
+ fp_t kvo_val = 0;
+
+ fp_t short_a = 2.0 / (k1 + 1);
+ fp_t long_a = 2.0 / (k10 + 1);
+ fp_t sig_a = 2.0 / (k1 + 1);
+
+ uint_t offset = k10;
+
+ for (uint_t i = 0; i < offset; ++i) { y[0][i] = 0; y[1][i] = 0; }
+
+ for( uint_t i = offset; i < n; ++i )
+ {
+ fp_t dvm_i = 0;
+ fp_t absdvm_i = 0;
+
+ if( i >= 1 )
+ {
+ fp_t dm = (ts_c(x, i) - ts_o(x, i)) - (ts_c(x, i-1) - ts_o(x, i-1));
+ fp_t vol = ts_v(x, i);
+ if( dm > 0 )
+ {
+ dvm_i = dm * vol;
+ absdvm_i = dm * vol;
+ }
+ else if( dm < 0 )
+ {
+ dvm_i = dm * vol;
+ absdvm_i = (-dm) * vol;
+ }
+ }
+
+ if( i == offset )
+ {
+ ema_short_dvm = dvm_i;
+ ema_long_absdvm = absdvm_i;
+ }
+ else
+ {
+ ema_short_dvm = ema_short_dvm + short_a * (dvm_i - ema_short_dvm);
+ ema_long_absdvm = ema_long_absdvm + long_a * (absdvm_i - ema_long_absdvm);
+ }
+
+ if( ema_long_absdvm != 0 )
+ kvo_val = ema_short_dvm / ema_long_absdvm;
+
+ if( i == offset )
+ ema_signal = kvo_val;
+ else
+ ema_signal = ema_signal + sig_a * (kvo_val - ema_signal);
+
+ y[0][i] = kvo_val;
+ y[1][i] = ema_signal;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/lag.c b/lib/lag.c
new file mode 100644
index 0000000..1e3f657
--- /dev/null
+++ b/lib/lag.c
@@ -0,0 +1,54 @@
+// Lag
+//
+// lag - lag
+//
+// DESCRIPTION
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 2
+
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+ fp_t insert;
+} ts_lag_params_t;
+
+ts_parameter_t const ts_lag_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 2, .params_size = sizeof(ts_lag_params_t) };
+
+
+uint_t ts_lag_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_lag_params_t *)c)->k : 0 );
+}
+
+int ts_lag(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_lag_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_lag_params_t *)c)->k;
+ if (k == 0) return -1;
+
+ fp_t insert = ((ts_lag_params_t *)c)->insert;
+
+ if (n <= k) return -1;
+
+ for (uint_t i = 0; i < k; ++i)
+ y[0][i] = insert;
+
+ for (uint_t i = k; i < n; ++i)
+ y[0][i] = x[0][i - k];
+ return 0;
+}
+
+
+
diff --git a/lib/laguerre.c b/lib/laguerre.c
new file mode 100644
index 0000000..b1e1284
--- /dev/null
+++ b/lib/laguerre.c
@@ -0,0 +1,62 @@
+// Laguerre
+//
+// laguerre
+//
+// Laguerre polynomial
+//
+// L_n(x) via recurrence relation
+//
+// Inputs: 2
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_laguerre_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_laguerre_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_laguerre(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_laguerre_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xv = x[0][i];
+ if (x[1][i] < 0) { y[0][i] = 0; continue; }
+ uint_t n_val = (uint_t)x[1][i];
+ if( n_val == 0 )
+ {
+ y[0][i] = 1.0;
+ }
+ else if( n_val == 1 )
+ {
+ y[0][i] = 1.0 - xv;
+ }
+ else
+ {
+ fp_t l0 = 1.0;
+ fp_t l1 = 1.0 - xv;
+ for( uint_t k = 2; k <= n_val; ++k )
+ {
+ fp_t l2 = ((2.0 * (fp_t)k - 1.0 - xv) * l1 - (fp_t)(k - 1) * l0) / (fp_t)k;
+ l0 = l1;
+ l1 = l2;
+ }
+ y[0][i] = l1;
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/lambert_w.c b/lib/lambert_w.c
new file mode 100644
index 0000000..da59199
--- /dev/null
+++ b/lib/lambert_w.c
@@ -0,0 +1,64 @@
+// Lambert W
+//
+// lambert_w
+//
+// Lambert W function
+//
+// W(x) such that W*e^W = x
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_lambert_w_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_lambert_w_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_lambert_w(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_lambert_w_p );
+ (void)c;
+ for (uint_t i = 0; i < n; ++i)
+ {
+ fp_t xv = x[0][i];
+ if (xv < -1.0/exp(1.0))
+ {
+ y[0][i] = NAN;
+ continue;
+ }
+ if (xv == 0.0)
+ {
+ y[0][i] = 0.0;
+ continue;
+ }
+ /* Halley iteration for W(x) */
+ fp_t w = xv < 1.0 ? log(xv + 1.0) : log(xv);
+ for (uint_t j = 0; j < 10; ++j)
+ {
+ fp_t ew = exp(w);
+ fp_t f = w * ew - xv;
+ fp_t f1 = ew * (w + 1.0);
+ if (f1 == 0) break;
+ fp_t f2 = f1 * (w + 2.0);
+ w = w - f / f1 * (1.0 + f * f2 / (2.0 * f1 * f1));
+ }
+ y[0][i] = w;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/laplace_cdf.c b/lib/laplace_cdf.c
new file mode 100644
index 0000000..7a70882
--- /dev/null
+++ b/lib/laplace_cdf.c
@@ -0,0 +1,43 @@
+// Laplace Cdf
+//
+// laplace_cdf
+//
+// Laplace cumulative distribution
+//
+// F(x) = 0.5 + 0.5*sign(x)*(1-e^(-|x|))
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_laplace_cdf_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_laplace_cdf_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_laplace_cdf(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_laplace_cdf_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xi = x[0][i];
+ y[0][i] = xi < 0 ? 0.5 * exp( xi ) : 1.0 - 0.5 * exp( -xi );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/laplace_pdf.c b/lib/laplace_pdf.c
new file mode 100644
index 0000000..d741aa4
--- /dev/null
+++ b/lib/laplace_pdf.c
@@ -0,0 +1,42 @@
+// Laplace Pdf
+//
+// laplace_pdf
+//
+// Laplace probability density
+//
+// f(x) = 0.5 * e^(-|x|)
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_laplace_pdf_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_laplace_pdf_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_laplace_pdf(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_laplace_pdf_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = exp( -fabs( x[0][i] ) ) / 2.0;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/lcm.c b/lib/lcm.c
new file mode 100644
index 0000000..c8b52ef
--- /dev/null
+++ b/lib/lcm.c
@@ -0,0 +1,62 @@
+// Lcm
+//
+// lcm
+//
+// Least common multiple
+//
+// lcm(a,b) = |a*b| / gcd(a,b)
+//
+// Inputs: 2
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_lcm_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_lcm_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_lcm(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_lcm_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t ax = fabs(x[0][i]);
+ fp_t bx = fabs(x[1][i]);
+ if (ax > 1e15 || bx > 1e15) { y[0][i] = 0.0; continue; }
+ long a = (long)ax;
+ long b = (long)bx;
+ if( !a || !b )
+ {
+ y[0][i] = 0.0;
+ }
+ else
+ {
+ long ta = a;
+ long tb = b;
+ while( tb )
+ {
+ long t = tb;
+ tb = ta % tb;
+ ta = t;
+ }
+ y[0][i] = (fp_t)(a / ta) * (fp_t)b;
+ }
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/leaky_relu.c b/lib/leaky_relu.c
new file mode 100644
index 0000000..51651bc
--- /dev/null
+++ b/lib/leaky_relu.c
@@ -0,0 +1,42 @@
+// Leaky ReLU
+//
+// leaky_relu - Leaky Rectified Linear Unit
+//
+// y[t] = x[t] > 0 ? x[t] : 0.01 * x[t]
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_leaky_relu_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_leaky_relu_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_leaky_relu(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_leaky_relu_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xi = x[0][i];
+ y[0][i] = xi > 0 ? xi : 0.01 * xi;
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/legendre.c b/lib/legendre.c
new file mode 100644
index 0000000..6ba4644
--- /dev/null
+++ b/lib/legendre.c
@@ -0,0 +1,64 @@
+// Legendre
+//
+// legendre
+//
+// Legendre polynomial
+//
+// P_n(x) via recurrence relation
+//
+// Inputs: 2
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_legendre_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_legendre_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_legendre(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_legendre_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xv = x[0][i];
+ if (x[1][i] < 0) { y[0][i] = 0; continue; }
+ uint_t n_val = (uint_t)x[1][i];
+ if( n_val == 0 )
+ {
+ y[0][i] = 1.0;
+ }
+ else if( n_val == 1 )
+ {
+ y[0][i] = xv;
+ }
+ else
+ {
+ fp_t p0 = 1.0;
+ fp_t p1 = xv;
+ for( uint_t k = 2; k <= n_val; ++k )
+ {
+ fp_t p2 = ((2.0 * (fp_t)k - 1.0) * xv * p1 - (fp_t)(k - 1) * p0) / (fp_t)k;
+ p0 = p1;
+ p1 = p2;
+ }
+ y[0][i] = p1;
+ }
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/lerp.c b/lib/lerp.c
new file mode 100644
index 0000000..6b284d6
--- /dev/null
+++ b/lib/lerp.c
@@ -0,0 +1,42 @@
+// Lerp
+//
+// lerp
+//
+// Linear interpolation
+//
+// a + t*(b-a)
+//
+// Inputs: 3
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_lerp_p = { .n_inputs = 3, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_lerp_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_lerp(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_lerp_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = x[0][i] + x[1][i] * ( x[2][i] - x[0][i] );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/lgamma.c b/lib/lgamma.c
new file mode 100644
index 0000000..02ac593
--- /dev/null
+++ b/lib/lgamma.c
@@ -0,0 +1,52 @@
+// Lgamma
+//
+// lgamma
+//
+// Natural log of gamma function
+//
+// ln(|Γ(x)|)
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_lgamma_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_lgamma_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_lgamma(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_lgamma_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xv = x[0][i];
+ if( xv <= 0.0 )
+ {
+ fp_t intpart;
+ if (modf(xv, &intpart) != 0.0)
+ y[0][i] = lgamma(xv);
+ else
+ y[0][i] = NAN;
+ }
+ else
+ y[0][i] = lgamma( xv );
+ }
+ return 0;
+}
+
+
+
diff --git a/lib/linear_regression.c b/lib/linear_regression.c
new file mode 100644
index 0000000..5d595bc
--- /dev/null
+++ b/lib/linear_regression.c
@@ -0,0 +1,86 @@
+// Linear Regression
+//
+// linear_regression
+//
+// Linear regression line
+//
+// y = a + b*x via least squares
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_linear_regression_params_t;
+
+ts_parameter_t const ts_linear_regression_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_linear_regression_params_t) };
+
+uint_t ts_linear_regression_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_linear_regression_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_linear_regression(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_linear_regression_p );
+ if ( !c ) return -1;
+ TS_CHECK_INPUT(x, x_n, n);
+ uint_t k = ((ts_linear_regression_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ // Precompute constant x sums (x values are always 0..k-1)
+ fp_t sum_x = 0, sum_x2 = 0;
+ for ( uint_t j = 0; j < k; ++j )
+ {
+ fp_t xv = (fp_t)j;
+ sum_x += xv;
+ sum_x2 += xv * xv;
+ }
+ fp_t denom = (fp_t)k * sum_x2 - sum_x * sum_x;
+ fp_t fk = (fp_t)k;
+
+ // Initial window
+ fp_t sum_y = 0, sum_xy = 0;
+ for ( uint_t j = 0; j < k; ++j )
+ {
+ fp_t yv = x[0][j];
+ sum_y += yv;
+ sum_xy += (fp_t)j * yv;
+ }
+ if (denom == 0) {
+ y[0][k - 1] = sum_y / fk;
+ } else {
+ fp_t slope = (fk * sum_xy - sum_x * sum_y) / denom;
+ fp_t intercept = (sum_y - slope * sum_x) / fk;
+ y[0][k - 1] = intercept + slope * (fp_t)(k - 1);
+ }
+
+ // Sliding window: sum_xy_new = sum_xy_old - sum_y_new + k * y_new
+ for ( uint_t i = k; i < n; ++i )
+ {
+ fp_t y_new = x[0][i];
+ fp_t y_old = x[0][i - k];
+ fp_t sum_y_old = sum_y;
+ sum_y = sum_y - y_old + y_new;
+ sum_xy = sum_xy - sum_y_old + y_old + (fk - 1) * y_new;
+ if (denom == 0) {
+ y[0][i] = sum_y / fk;
+ } else {
+ fp_t slope = (fk * sum_xy - sum_x * sum_y) / denom;
+ fp_t intercept = (sum_y - slope * sum_x) / fk;
+ y[0][i] = intercept + slope * (fp_t)(k - 1);
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/linear_regression_angle.c b/lib/linear_regression_angle.c
new file mode 100644
index 0000000..22928bc
--- /dev/null
+++ b/lib/linear_regression_angle.c
@@ -0,0 +1,80 @@
+// Linear Regression Angle
+//
+// linear_regression_angle
+//
+// Linear regression angle
+//
+// atan(slope) in degrees
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_linear_regression_angle_params_t;
+
+ts_parameter_t const ts_linear_regression_angle_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_linear_regression_angle_params_t) };
+
+uint_t ts_linear_regression_angle_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_linear_regression_angle_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_linear_regression_angle(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_linear_regression_angle_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_linear_regression_angle_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ fp_t sum_x = 0, sum_x2 = 0;
+ for ( uint_t j = 0; j < k; ++j )
+ {
+ fp_t xv = (fp_t)j;
+ sum_x += xv;
+ sum_x2 += xv * xv;
+ }
+ fp_t denom = (fp_t)k * sum_x2 - sum_x * sum_x;
+ fp_t fk = (fp_t)k;
+
+ fp_t sum_y = 0, sum_xy = 0;
+ for ( uint_t j = 0; j < k; ++j )
+ {
+ fp_t yv = x[0][j];
+ sum_y += yv;
+ sum_xy += (fp_t)j * yv;
+ }
+ if (denom == 0) {
+ y[0][k - 1] = 0;
+ } else {
+ fp_t slope = (fk * sum_xy - sum_x * sum_y) / denom;
+ y[0][k - 1] = atan(slope) * 180.0 / PI;
+ }
+
+ for ( uint_t i = k; i < n; ++i )
+ {
+ fp_t y_new = x[0][i];
+ fp_t y_old = x[0][i - k];
+ fp_t sum_y_old = sum_y;
+ sum_y = sum_y - y_old + y_new;
+ sum_xy = sum_xy - sum_y_old + y_old + (fk - 1) * y_new;
+ if (denom == 0) {
+ y[0][i] = 0;
+ } else {
+ fp_t slope = (fk * sum_xy - sum_x * sum_y) / denom;
+ y[0][i] = atan(slope) * 180.0 / PI;
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/linear_regression_forecast.c b/lib/linear_regression_forecast.c
new file mode 100644
index 0000000..02a78a8
--- /dev/null
+++ b/lib/linear_regression_forecast.c
@@ -0,0 +1,82 @@
+// Linear Regression Forecast
+//
+// linear_regression_forecast
+//
+// Linear regression forecast
+//
+// Forecast = intercept + slope * period
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_linear_regression_forecast_params_t;
+
+ts_parameter_t const ts_linear_regression_forecast_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_linear_regression_forecast_params_t) };
+
+uint_t ts_linear_regression_forecast_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_linear_regression_forecast_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_linear_regression_forecast(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_linear_regression_forecast_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_linear_regression_forecast_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ fp_t sum_x = 0, sum_x2 = 0;
+ for ( uint_t j = 0; j < k; ++j )
+ {
+ fp_t xv = (fp_t)j;
+ sum_x += xv;
+ sum_x2 += xv * xv;
+ }
+ fp_t denom = (fp_t)k * sum_x2 - sum_x * sum_x;
+ fp_t fk = (fp_t)k;
+
+ fp_t sum_y = 0, sum_xy = 0;
+ for ( uint_t j = 0; j < k; ++j )
+ {
+ fp_t yv = x[0][j];
+ sum_y += yv;
+ sum_xy += (fp_t)j * yv;
+ }
+ if (denom == 0) {
+ y[0][k - 1] = sum_y / fk;
+ } else {
+ fp_t slope = (fk * sum_xy - sum_x * sum_y) / denom;
+ fp_t intercept = (sum_y - slope * sum_x) / fk;
+ y[0][k - 1] = intercept + slope * fk;
+ }
+
+ for ( uint_t i = k; i < n; ++i )
+ {
+ fp_t y_new = x[0][i];
+ fp_t y_old = x[0][i - k];
+ fp_t sum_y_old = sum_y;
+ sum_y = sum_y - y_old + y_new;
+ sum_xy = sum_xy - sum_y_old + y_old + (fk - 1) * y_new;
+ if (denom == 0) {
+ y[0][i] = sum_y / fk;
+ } else {
+ fp_t slope = (fk * sum_xy - sum_x * sum_y) / denom;
+ fp_t intercept = (sum_y - slope * sum_x) / fk;
+ y[0][i] = intercept + slope * fk;
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/linear_regression_intercept.c b/lib/linear_regression_intercept.c
new file mode 100644
index 0000000..2b26478
--- /dev/null
+++ b/lib/linear_regression_intercept.c
@@ -0,0 +1,79 @@
+// Linear Regression Intercept
+//
+// linear_regression_intercept
+//
+// Linear regression intercept
+//
+// a = ȳ - b*x̄
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_linear_regression_intercept_params_t;
+
+ts_parameter_t const ts_linear_regression_intercept_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_linear_regression_intercept_params_t) };
+
+uint_t ts_linear_regression_intercept_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_linear_regression_intercept_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_linear_regression_intercept(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_linear_regression_intercept_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_linear_regression_intercept_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ fp_t sum_x = 0, sum_x2 = 0;
+ for ( uint_t j = 0; j < k; ++j )
+ {
+ fp_t xv = (fp_t)j;
+ sum_x += xv;
+ sum_x2 += xv * xv;
+ }
+ fp_t denom = (fp_t)k * sum_x2 - sum_x * sum_x;
+ fp_t fk = (fp_t)k;
+
+ fp_t sum_y = 0, sum_xy = 0;
+ for ( uint_t j = 0; j < k; ++j )
+ {
+ fp_t yv = x[0][j];
+ sum_y += yv;
+ sum_xy += (fp_t)j * yv;
+ }
+ if (denom == 0) {
+ y[0][k - 1] = sum_y / fk;
+ } else {
+ fp_t slope = (fk * sum_xy - sum_x * sum_y) / denom;
+ y[0][k - 1] = (sum_y - slope * sum_x) / fk;
+ }
+
+ for ( uint_t i = k; i < n; ++i )
+ {
+ fp_t y_new = x[0][i];
+ fp_t y_old = x[0][i - k];
+ fp_t sum_y_old = sum_y;
+ sum_y = sum_y - y_old + y_new;
+ sum_xy = sum_xy - sum_y_old + y_old + (fk - 1) * y_new;
+ if (denom == 0) {
+ y[0][i] = sum_y / fk;
+ } else {
+ fp_t slope = (fk * sum_xy - sum_x * sum_y) / denom;
+ y[0][i] = (sum_y - slope * sum_x) / fk;
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/linear_regression_r2.c b/lib/linear_regression_r2.c
new file mode 100644
index 0000000..5cec662
--- /dev/null
+++ b/lib/linear_regression_r2.c
@@ -0,0 +1,88 @@
+// Linear Regression R2
+//
+// linear_regression_r2
+//
+// Linear regression R-squared
+//
+// R² = 1 - SS_res/SS_tot
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_linear_regression_r2_params_t;
+
+ts_parameter_t const ts_linear_regression_r2_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_linear_regression_r2_params_t) };
+
+uint_t ts_linear_regression_r2_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_linear_regression_r2_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_linear_regression_r2(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_linear_regression_r2_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_linear_regression_r2_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ fp_t sum_x = 0, sum_x2 = 0;
+ for ( uint_t j = 0; j < k; ++j )
+ {
+ fp_t xv = (fp_t)j;
+ sum_x += xv;
+ sum_x2 += xv * xv;
+ }
+ fp_t fk = (fp_t)k;
+ fp_t denom_x = fk * sum_x2 - sum_x * sum_x;
+
+ fp_t sum_y = 0, sum_xy = 0, sum_y2 = 0;
+ for ( uint_t j = 0; j < k; ++j )
+ {
+ fp_t yv = x[0][j];
+ sum_y += yv;
+ sum_xy += (fp_t)j * yv;
+ sum_y2 += yv * yv;
+ }
+ fp_t denom_y = fk * sum_y2 - sum_y * sum_y;
+ fp_t num = fk * sum_xy - sum_x * sum_y;
+ if (denom_x == 0 || denom_y == 0) {
+ y[0][k - 1] = 0;
+ } else {
+ fp_t prod = denom_x * denom_y;
+ fp_t r = prod > 0 ? num / sqrt(prod) : 0;
+ y[0][k - 1] = r * r;
+ }
+
+ for ( uint_t i = k; i < n; ++i )
+ {
+ fp_t y_new = x[0][i];
+ fp_t y_old = x[0][i - k];
+ fp_t sum_y_old = sum_y;
+ sum_y = sum_y - y_old + y_new;
+ sum_xy = sum_xy - sum_y_old + y_old + (fk - 1) * y_new;
+ sum_y2 = sum_y2 - y_old * y_old + y_new * y_new;
+ denom_y = fk * sum_y2 - sum_y * sum_y;
+ num = fk * sum_xy - sum_x * sum_y;
+ if (denom_x == 0 || denom_y == 0) {
+ y[0][i] = 0;
+ } else {
+ fp_t prod = denom_x * denom_y;
+ fp_t r = prod > 0 ? num / sqrt(prod) : 0;
+ y[0][i] = r * r;
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/linear_regression_slope.c b/lib/linear_regression_slope.c
new file mode 100644
index 0000000..d20ca8e
--- /dev/null
+++ b/lib/linear_regression_slope.c
@@ -0,0 +1,77 @@
+// Linear Regression Slope
+//
+// linear_regression_slope
+//
+// Linear regression slope
+//
+// b = Σ(x-x̄)(y-ȳ) / Σ(x-x̄)²
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_linear_regression_slope_params_t;
+
+ts_parameter_t const ts_linear_regression_slope_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_linear_regression_slope_params_t) };
+
+uint_t ts_linear_regression_slope_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_linear_regression_slope_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_linear_regression_slope(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_linear_regression_slope_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_linear_regression_slope_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ fp_t sum_x = 0, sum_x2 = 0;
+ for ( uint_t j = 0; j < k; ++j )
+ {
+ fp_t xv = (fp_t)j;
+ sum_x += xv;
+ sum_x2 += xv * xv;
+ }
+ fp_t denom = (fp_t)k * sum_x2 - sum_x * sum_x;
+ fp_t fk = (fp_t)k;
+
+ fp_t sum_y = 0, sum_xy = 0;
+ for ( uint_t j = 0; j < k; ++j )
+ {
+ fp_t yv = x[0][j];
+ sum_y += yv;
+ sum_xy += (fp_t)j * yv;
+ }
+ if (denom == 0) {
+ y[0][k - 1] = 0;
+ } else {
+ y[0][k - 1] = (fk * sum_xy - sum_x * sum_y) / denom;
+ }
+
+ for ( uint_t i = k; i < n; ++i )
+ {
+ fp_t y_new = x[0][i];
+ fp_t y_old = x[0][i - k];
+ fp_t sum_y_old = sum_y;
+ sum_y = sum_y - y_old + y_new;
+ sum_xy = sum_xy - sum_y_old + y_old + (fk - 1) * y_new;
+ if (denom == 0) {
+ y[0][i] = 0;
+ } else {
+ y[0][i] = (fk * sum_xy - sum_x * sum_y) / denom;
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/linear_weighted_ma.c b/lib/linear_weighted_ma.c
new file mode 100644
index 0000000..70f660e
--- /dev/null
+++ b/lib/linear_weighted_ma.c
@@ -0,0 +1,56 @@
+// Linear Weighted Ma
+//
+// linear_weighted_ma
+//
+// Linear Weighted Moving Average
+//
+// LWMA = Σ(i*x_i) / Σ(i)
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_linear_weighted_ma_params_t;
+
+ts_parameter_t const ts_linear_weighted_ma_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_linear_weighted_ma_params_t) };
+
+uint_t ts_linear_weighted_ma_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_linear_weighted_ma_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_linear_weighted_ma(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_linear_weighted_ma_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_linear_weighted_ma_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ fp_t w_sum = 0;
+ for( uint_t j = 1; j <= k; ++j )
+ {
+ w_sum += (fp_t)j;
+ }
+
+ for( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t sum = 0;
+ for( uint_t j = 0; j < k; ++j )
+ {
+ sum += (fp_t)(j + 1) * x[0][i - k + 1 + j];
+ }
+ y[0][i] = sum / w_sum;
+ }
+ return 0;
+}
+
diff --git a/lib/log1p.c b/lib/log1p.c
new file mode 100644
index 0000000..dc49223
--- /dev/null
+++ b/lib/log1p.c
@@ -0,0 +1,41 @@
+// Log1p
+//
+// log1p
+//
+// Log(1+x)
+//
+// ln(1+x), accurate for small x
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+ts_parameter_t const ts_log1p_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_log1p_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_log1p(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_log1p_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xv = x[0][i];
+ if( xv < -1.0 ) { y[0][i] = 0; continue; }
+ y[0][i] = log1p( xv );
+ }
+ return 0;
+}
+
diff --git a/lib/log1pm1.c b/lib/log1pm1.c
new file mode 100644
index 0000000..ea9281a
--- /dev/null
+++ b/lib/log1pm1.c
@@ -0,0 +1,41 @@
+// Log1pm1
+//
+// log1pm1
+//
+// Log(1+x) - 1
+//
+// ln(1+x) - 1
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+ts_parameter_t const ts_log1pm1_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_log1pm1_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_log1pm1(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_log1pm1_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xv = x[0][i];
+ if( xv < -1.0 ) { y[0][i] = 0; continue; }
+ y[0][i] = log1p( xv ) - 1.0;
+ }
+ return 0;
+}
+
diff --git a/lib/log_return.c b/lib/log_return.c
new file mode 100644
index 0000000..2aa8334
--- /dev/null
+++ b/lib/log_return.c
@@ -0,0 +1,50 @@
+// Log Return
+//
+// log_return
+//
+// Logarithmic return
+//
+// ln(C[t] / C[t-1])
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_log_return_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_log_return_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 3 );
+}
+
+int ts_log_return(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_log_return_p );
+ (void)c;
+ if (n < 3) return -1;
+ for (uint_t i = 0; i < 3; ++i) y[0][i] = 0;
+
+ for( uint_t i = 3; i < n; ++i )
+ {
+ if ( x[0][i - 3] <= 0 || x[0][i] <= 0 )
+ {
+ y[0][i] = NAN;
+ }
+ else
+ {
+ y[0][i] = log( x[0][i] / x[0][i - 3] );
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/logarithm.c b/lib/logarithm.c
new file mode 100755
index 0000000..7451976
--- /dev/null
+++ b/lib/logarithm.c
@@ -0,0 +1,50 @@
+// Logarithm
+//
+// logarithm - log
+//
+// f( x ) = log_b( x )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 1
+//
+// c[0] = b
+
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ fp_t b;
+} ts_logarithm_params_t;
+
+ts_parameter_t const ts_logarithm_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_logarithm_params_t) };
+
+
+uint_t ts_logarithm_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_logarithm(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_logarithm_p );
+ if ( !c ) return -1;
+ fp_t b = ((ts_logarithm_params_t *)c)->b;
+ if (b == 0 || b == 1.0) return -1;
+
+ b = _ts_reciprocal( log( b ) );
+
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = x[0][i] > 0 ? log( x[0][i] ) * b : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/logarithm_binary.c b/lib/logarithm_binary.c
new file mode 100644
index 0000000..1c6900d
--- /dev/null
+++ b/lib/logarithm_binary.c
@@ -0,0 +1,44 @@
+// Binary Logarithm
+//
+// logarithm_binary - logb, lb
+//
+// f( x ) = log_2( x )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_logarithm_binary_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_logarithm_binary_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_logarithm_binary(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_logarithm_binary_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ if (x[0][i] <= 0) {
+ y[0][i] = 0;
+ } else {
+ y[0][i] = log2( x[0][i] );
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/logarithm_decimal.c b/lib/logarithm_decimal.c
new file mode 100644
index 0000000..39afb47
--- /dev/null
+++ b/lib/logarithm_decimal.c
@@ -0,0 +1,44 @@
+// Decimal Logarithm
+//
+// logarithm_decimal - logd, ld
+//
+// f( x ) = log_10( x )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_logarithm_decimal_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_logarithm_decimal_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_logarithm_decimal(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_logarithm_decimal_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ if (x[0][i] <= 0) {
+ y[0][i] = 0;
+ } else {
+ y[0][i] = log10( x[0][i] );
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/logarithm_natural.c b/lib/logarithm_natural.c
new file mode 100644
index 0000000..b76d67d
--- /dev/null
+++ b/lib/logarithm_natural.c
@@ -0,0 +1,45 @@
+// Natural Logarithm
+//
+// logarithm_natural - logn, ln
+//
+// f( x ) = log_e( x )
+// = ln( x )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_logarithm_natural_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_logarithm_natural_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_logarithm_natural(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_logarithm_natural_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ if (x[0][i] <= 0) {
+ y[0][i] = 0;
+ } else {
+ y[0][i] = log( x[0][i] );
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/logb.c b/lib/logb.c
new file mode 100644
index 0000000..7f1b66e
--- /dev/null
+++ b/lib/logb.c
@@ -0,0 +1,42 @@
+// Logb
+//
+// logb
+//
+// Logarithm base of floating point
+//
+// exponent of the floating-point representation
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_logb_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_logb_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_logb(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_logb_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xv = x[0][i];
+ if( xv == 0.0 ) { y[0][i] = 0; continue; }
+ y[0][i] = (fp_t)logb( xv );
+ }
+ return 0;
+}
+
diff --git a/lib/lowest_low.c b/lib/lowest_low.c
new file mode 100644
index 0000000..d8ac284
--- /dev/null
+++ b/lib/lowest_low.c
@@ -0,0 +1,66 @@
+// Lowest Low
+//
+// lowest_low
+//
+// Lowest low over rolling window
+//
+// LL = min(L[t-k+1..t])
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_lowest_low_params_t;
+
+ts_parameter_t const ts_lowest_low_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_lowest_low_params_t) };
+
+uint_t ts_lowest_low_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_lowest_low_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_lowest_low(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_lowest_low_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_lowest_low_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ fp_t min_val = ts_l( x, 0 );
+ for( uint_t j = 1; j < k; ++j )
+ {
+ fp_t val = ts_l( x, j );
+ if( val < min_val )
+ min_val = val;
+ }
+ y[0][k - 1] = min_val;
+
+ for( uint_t i = k; i < n; ++i )
+ {
+ fp_t new_val = ts_l( x, i );
+ fp_t old_val = ts_l( x, i - k );
+ if ( new_val < min_val ) min_val = new_val;
+ if ( old_val == min_val )
+ {
+ min_val = ts_l( x, i - k + 1 );
+ for( uint_t j = i - k + 2; j <= i; ++j )
+ {
+ fp_t val = ts_l( x, j );
+ if( val < min_val ) min_val = val;
+ }
+ }
+ y[0][i] = min_val;
+ }
+ return 0;
+}
+
diff --git a/lib/macd.c b/lib/macd.c
new file mode 100644
index 0000000..362f251
--- /dev/null
+++ b/lib/macd.c
@@ -0,0 +1,87 @@
+// Macd
+//
+// macd
+//
+// Moving Average Convergence Divergence
+//
+// MACD = EMA(fast) - EMA(slow), Signal = EMA(MACD)
+//
+// Inputs: 1
+// Outputs: 3
+// Constants: 3
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ fp_t fast_k;
+ fp_t slow_k;
+ fp_t signal_k;
+} ts_macd_params_t;
+
+ts_parameter_t const ts_macd_p = { .n_inputs = 1, .n_outputs = 3, .n_constants = 3, .params_size = sizeof(ts_macd_params_t) };
+
+uint_t ts_macd_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? (uint_t)(((ts_macd_params_t *)c)->slow_k + ((ts_macd_params_t *)c)->signal_k - 2) : 0 );
+}
+
+int ts_macd(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_macd_p );
+ if ( !c ) return -1;
+ TS_CHECK_INPUT(x, x_n, n);
+ fp_t fast_k = ((ts_macd_params_t *)c)->fast_k;
+ fp_t slow_k = ((ts_macd_params_t *)c)->slow_k;
+ fp_t signal_k = ((ts_macd_params_t *)c)->signal_k;
+
+ if (fast_k < 1 || slow_k < 1 || signal_k < 1) return -1;
+ if (isnan(fast_k) || isnan(slow_k) || isnan(signal_k)) return -1;
+ if (n == 0) return -1;
+
+ fp_t fast_a = 2.0 / (fast_k + 1.0);
+ fp_t slow_a = 2.0 / (slow_k + 1.0);
+ fp_t signal_a = 2.0 / (signal_k + 1.0);
+
+ fp_t fast_ema = x[0][0];
+ fp_t slow_ema = x[0][0];
+
+ uint_t total_offset = (uint_t)(slow_k + signal_k - 2);
+ if (n <= total_offset) return -1;
+
+ // Compute EMAs up to total_offset
+ for( uint_t i = 1; i <= total_offset; ++i )
+ {
+ fast_ema = fast_ema + fast_a * ( x[0][i] - fast_ema );
+ slow_ema = slow_ema + slow_a * ( x[0][i] - slow_ema );
+ }
+
+ fp_t macd_line = fast_ema - slow_ema;
+ fp_t signal_ema = macd_line;
+
+ y[0][total_offset] = macd_line;
+ y[1][total_offset] = signal_ema;
+ y[2][total_offset] = 0;
+
+ for( uint_t i = total_offset + 1; i < n; ++i )
+ {
+ fast_ema = fast_ema + fast_a * ( x[0][i] - fast_ema );
+ slow_ema = slow_ema + slow_a * ( x[0][i] - slow_ema );
+
+ macd_line = fast_ema - slow_ema;
+ signal_ema = signal_ema + signal_a * ( macd_line - signal_ema );
+
+ y[0][i] = macd_line;
+ y[1][i] = signal_ema;
+ y[2][i] = macd_line - signal_ema;
+ }
+
+ return 0;
+}
+
diff --git a/lib/manhattan_distance.c b/lib/manhattan_distance.c
new file mode 100644
index 0000000..caa5ee4
--- /dev/null
+++ b/lib/manhattan_distance.c
@@ -0,0 +1,40 @@
+// Manhattan Distance
+//
+// manhattan_distance
+//
+// Manhattan distance (L1 norm)
+//
+//
+//
+// Inputs: 2
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_manhattan_distance_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_manhattan_distance_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_manhattan_distance(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_manhattan_distance_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = fabs( x[0][i] ) + fabs( x[1][i] );
+ }
+ return 0;
+}
+
diff --git a/lib/manhattan_norm.c b/lib/manhattan_norm.c
new file mode 100644
index 0000000..10ca769
--- /dev/null
+++ b/lib/manhattan_norm.c
@@ -0,0 +1,40 @@
+// Manhattan Norm
+//
+// manhattan_norm
+//
+// Manhattan norm (L1)
+//
+//
+//
+// Inputs: 2
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+ts_parameter_t const ts_manhattan_norm_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_manhattan_norm_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_manhattan_norm(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_manhattan_norm_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = fabs( x[0][i] ) + fabs( x[1][i] );
+ }
+ return 0;
+}
+
diff --git a/lib/market_facilitation_index.c b/lib/market_facilitation_index.c
new file mode 100644
index 0000000..09c3feb
--- /dev/null
+++ b/lib/market_facilitation_index.c
@@ -0,0 +1,39 @@
+// Market Facilitation Index
+//
+// market_facilitation_index
+//
+// Market Facilitation Index
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_market_facilitation_index_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_market_facilitation_index_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_market_facilitation_index(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_market_facilitation_index_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = (x[4][i] != 0) ? (x[1][i] - x[2][i]) / x[4][i] : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/mass_index.c b/lib/mass_index.c
new file mode 100644
index 0000000..1985fab
--- /dev/null
+++ b/lib/mass_index.c
@@ -0,0 +1,91 @@
+// Mass Index
+//
+// mass_index
+//
+// Mass Index
+//
+// SMA of EMA(H/L) ratio, reversal when > threshold
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 0
+
+
+#include <stdlib.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_mass_index_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_mass_index_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 26 );
+}
+
+int ts_mass_index(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_mass_index_p );
+ (void)c;
+ uint_t ema_period = 9;
+ uint_t sum_period = 26;
+
+ if (n < sum_period) return -1;
+
+ fp_t *hl = calloc( n, sizeof( fp_t ) );
+ fp_t *ema1 = calloc( n, sizeof( fp_t ) );
+ fp_t *ema2 = calloc( n, sizeof( fp_t ) );
+ fp_t *ratio = calloc( n, sizeof( fp_t ) );
+ if ( !hl || !ema1 || !ema2 || !ratio )
+ {
+ free( hl ); free( ema1 ); free( ema2 ); free( ratio );
+ return -1;
+ }
+
+ fp_t a1 = 2.0 / ( ( fp_t )ema_period + 1.0 );
+
+ for ( uint_t i = 0; i < n; ++i )
+ {
+ hl[i] = ts_h( x, i ) - ts_l( x, i );
+ }
+
+ ema1[0] = hl[0];
+ for ( uint_t i = 1; i < n; ++i )
+ {
+ ema1[i] = ema1[i - 1] + a1 * ( hl[i] - ema1[i - 1] );
+ }
+
+ ema2[0] = ema1[0];
+ for ( uint_t i = 1; i < n; ++i )
+ {
+ ema2[i] = ema2[i - 1] + a1 * ( ema1[i] - ema2[i - 1] );
+ }
+
+ for ( uint_t i = 0; i < n; ++i )
+ {
+ ratio[i] = ema2[i] != 0 ? ema1[i] / ema2[i] : 0;
+ }
+
+ fp_t sum = 0;
+ for ( uint_t i = 0; i < sum_period; ++i )
+ {
+ sum += ratio[i];
+ }
+
+ for ( uint_t j = 0; j < sum_period - 1; ++j ) y[0][j] = 0;
+ y[0][sum_period - 1] = sum;
+
+ for ( uint_t i = sum_period; i < n; ++i )
+ {
+ sum = sum - ratio[i - sum_period] + ratio[i];
+ y[0][i] = sum;
+ }
+
+ free( hl ); free( ema1 ); free( ema2 ); free( ratio );
+ return 0;
+}
+
diff --git a/lib/maximum.c b/lib/maximum.c
new file mode 100755
index 0000000..f4d124a
--- /dev/null
+++ b/lib/maximum.c
@@ -0,0 +1,56 @@
+// Maximum
+//
+// maximum - max
+//
+// Finds the maximum value in the past k values
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+} ts_maximum_params_t;
+
+ts_parameter_t const ts_maximum_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_maximum_params_t) };
+
+
+uint_t ts_maximum_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_maximum_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_maximum(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_maximum_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_maximum_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ uint_t xi = k - 1;
+ uint_t xj = 0;
+ uint_t yi = k - 1;
+
+ for( ; xi < n; xi++, xj++, yi++ )
+ {
+ y[0][yi] = x[0][xj];
+
+ for( uint_t i = xj; i <= xi; i++ )
+ {
+ if( x[0][i] > y[0][yi] )
+ {
+ y[0][yi] = x[0][i];
+ }
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/median_price.c b/lib/median_price.c
new file mode 100644
index 0000000..7df202f
--- /dev/null
+++ b/lib/median_price.c
@@ -0,0 +1,39 @@
+// Median Price
+//
+// median_price
+//
+// Median Price
+//
+//
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_median_price_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_median_price_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_median_price(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_median_price_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = ( x[1][i] + x[2][i] ) / 2;
+ }
+ return 0;
+}
+
diff --git a/lib/mesa_sine_wave.c b/lib/mesa_sine_wave.c
new file mode 100644
index 0000000..8ed5fa6
--- /dev/null
+++ b/lib/mesa_sine_wave.c
@@ -0,0 +1,112 @@
+// Mesa Sine Wave
+//
+// mesa_sine_wave
+//
+// Mesa Sine Wave
+//
+// Adaptive sine wave following price trends
+//
+// Inputs: 1
+// Outputs: 2
+// Constants: 1
+
+
+#include <math.h>
+#include <stdlib.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_mesa_sine_wave_params_t;
+
+ts_parameter_t const ts_mesa_sine_wave_p = { .n_inputs = 1, .n_outputs = 2, .n_constants = 1, .params_size = sizeof(ts_mesa_sine_wave_params_t) };
+
+uint_t ts_mesa_sine_wave_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_mesa_sine_wave_params_t *)c)->k : 0 );
+}
+
+int ts_mesa_sine_wave(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_mesa_sine_wave_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_mesa_sine_wave_params_t *)c)->k;
+ if (k < 2 || n < k) return -1;
+
+ fp_t *sma = calloc(n, sizeof(fp_t));
+ if (!sma) return -1;
+
+ for (uint_t i = 0; i < n; ++i)
+ {
+ if (i < k - 1)
+ {
+ fp_t sum = 0;
+ for (uint_t j = 0; j <= i; ++j)
+ sum += x[0][j];
+ sma[i] = sum / (i + 1);
+ }
+ else
+ {
+ fp_t sum = 0;
+ for (uint_t j = i - k + 1; j <= i; ++j)
+ sum += x[0][j];
+ sma[i] = sum / k;
+ }
+ }
+
+ fp_t ema_roc = 0;
+ uint_t ema_period = 5;
+ fp_t ema_a = 2.0 / (ema_period + 1);
+ fp_t phase = 0;
+ int_t first = 1;
+
+ for (uint_t j = 0; j < n && j < k; ++j) { y[0][j] = 0; y[1][j] = 0; }
+ for (uint_t i = k; i < n; ++i)
+ {
+ fp_t roc = sma[i] - sma[i - 2];
+
+ if (first)
+ {
+ ema_roc = roc;
+ first = 0;
+ }
+ else
+ {
+ ema_roc = ema_roc + ema_a * (roc - ema_roc);
+ }
+
+ fp_t smoothed = ema_roc;
+ if (smoothed > 1.0) smoothed = 1.0;
+ if (smoothed < 0.01) smoothed = 0.01;
+
+ fp_t wperiod = 2 * PI / smoothed;
+ if (wperiod < 5) wperiod = 5;
+ if (wperiod > 12) wperiod = 12;
+
+ if (smoothed >= 1.0)
+ {
+ phase = 0;
+ }
+ else if (smoothed <= 0.01)
+ {
+ phase = 2 * PI;
+ }
+ else
+ {
+ phase += 2 * PI / wperiod;
+ }
+ while (phase > 2 * PI) phase -= 2 * PI;
+
+ y[0][i] = cos(phase);
+ y[1][i] = cos(phase + PI / 2);
+ }
+
+ free(sma);
+ return 0;
+}
+
diff --git a/lib/mfi.c b/lib/mfi.c
new file mode 100644
index 0000000..c93803c
--- /dev/null
+++ b/lib/mfi.c
@@ -0,0 +1,84 @@
+// Mfi
+//
+// mfi
+//
+// Money Flow Index
+//
+// 100 - 100/(1 + MF_ratio), volume-weighted RSI
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 1
+
+
+#include <stdlib.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_mfi_params_t;
+
+ts_parameter_t const ts_mfi_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_mfi_params_t) };
+
+uint_t ts_mfi_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_mfi_params_t *)c)->k : 0 );
+}
+
+int ts_mfi(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_mfi_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_mfi_params_t *)c)->k;
+ if (k == 0 || n <= k) return -1;
+
+ fp_t *tp = calloc( n, sizeof( fp_t ) );
+ if ( !tp ) return -1;
+
+ for( uint_t i = 0; i < n; ++i )
+ {
+ tp[i] = ( ts_h( x, i ) + ts_l( x, i ) + ts_c( x, i ) ) / 3.0;
+ }
+
+ fp_t pos_sum = 0;
+ fp_t neg_sum = 0;
+
+ for (uint_t i = 0; i < k; ++i) y[0][i] = 0;
+
+ for( uint_t i = 1; i <= k; ++i )
+ {
+ fp_t raw_mf = tp[i] * ts_v( x, i );
+ if( tp[i] > tp[i - 1] )
+ pos_sum += raw_mf;
+ else
+ neg_sum += raw_mf;
+ }
+
+ y[0][k] = ( neg_sum == 0 ) ? 100.0 : 100.0 - 100.0 / ( 1.0 + pos_sum / neg_sum );
+
+ for( uint_t i = k + 1; i < n; ++i )
+ {
+ fp_t cur_raw_mf = tp[i] * ts_v( x, i );
+ if( tp[i] > tp[i - 1] )
+ pos_sum += cur_raw_mf;
+ else
+ neg_sum += cur_raw_mf;
+
+ fp_t old_raw_mf = tp[i - k] * ts_v( x, i - k );
+ if( tp[i - k] > tp[i - k - 1] )
+ pos_sum -= old_raw_mf;
+ else
+ neg_sum -= old_raw_mf;
+
+ y[0][i] = ( neg_sum == 0 ) ? 100.0 : 100.0 - 100.0 / ( 1.0 + pos_sum / neg_sum );
+ }
+
+ free( tp );
+ return 0;
+}
+
diff --git a/lib/midpoint.c b/lib/midpoint.c
new file mode 100644
index 0000000..c28cdff
--- /dev/null
+++ b/lib/midpoint.c
@@ -0,0 +1,53 @@
+// Midpoint
+//
+// midpoint
+//
+// Midpoint over rolling window
+//
+// (highest_high + lowest_low) / 2
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_midpoint_params_t;
+
+ts_parameter_t const ts_midpoint_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_midpoint_params_t) };
+
+uint_t ts_midpoint_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_midpoint_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_midpoint(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_midpoint_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_midpoint_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t max_val = x[0][i];
+ fp_t min_val = x[0][i];
+ for( uint_t j = 1; j < k; ++j )
+ {
+ fp_t val = x[0][i - j];
+ if( val > max_val ) max_val = val;
+ if( val < min_val ) min_val = val;
+ }
+ y[0][i] = ( max_val + min_val ) / 2;
+ }
+ return 0;
+}
+
diff --git a/lib/midprice.c b/lib/midprice.c
new file mode 100644
index 0000000..d26a58e
--- /dev/null
+++ b/lib/midprice.c
@@ -0,0 +1,53 @@
+// Midprice
+//
+// midprice
+//
+// Midprice
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_midprice_params_t;
+
+ts_parameter_t const ts_midprice_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_midprice_params_t) };
+
+uint_t ts_midprice_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_midprice_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_midprice(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_midprice_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_midprice_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t max_val = x[0][i];
+ fp_t min_val = x[0][i];
+ for( uint_t j = 1; j < k; ++j )
+ {
+ fp_t val = x[0][i - j];
+ if( val > max_val ) max_val = val;
+ if( val < min_val ) min_val = val;
+ }
+ y[0][i] = ( max_val + min_val ) / 2;
+ }
+ return 0;
+}
+
diff --git a/lib/minimum.c b/lib/minimum.c
new file mode 100755
index 0000000..d2ba15f
--- /dev/null
+++ b/lib/minimum.c
@@ -0,0 +1,56 @@
+// Minimum
+//
+// minimum - min
+//
+// Finds the minimum value in the past k data points
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+} ts_minimum_params_t;
+
+ts_parameter_t const ts_minimum_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_minimum_params_t) };
+
+
+uint_t ts_minimum_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_minimum_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_minimum(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_minimum_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_minimum_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ uint_t xi = k - 1;
+ uint_t xj = 0;
+ uint_t yi = k - 1;
+
+ for( ; xi < n; xi++, xj++, yi++ )
+ {
+ y[0][yi] = x[0][xj];
+
+ for( uint_t i = xj; i <= xi; i++ )
+ {
+ if( x[0][i] < y[0][yi] )
+ {
+ y[0][yi] = x[0][i];
+ }
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/minmax.c b/lib/minmax.c
new file mode 100644
index 0000000..4bee598
--- /dev/null
+++ b/lib/minmax.c
@@ -0,0 +1,53 @@
+// Minmax
+//
+// minmax
+//
+// Min and Max over rolling window
+//
+// min(x[t-k+1..t]), max(x[t-k+1..t])
+//
+// Inputs: 1
+// Outputs: 2
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_minmax_params_t;
+
+ts_parameter_t const ts_minmax_p = { .n_inputs = 1, .n_outputs = 2, .n_constants = 1, .params_size = sizeof(ts_minmax_params_t) };
+
+uint_t ts_minmax_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_minmax_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_minmax(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_minmax_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_minmax_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t min_val = x[0][i - k + 1];
+ fp_t max_val = x[0][i - k + 1];
+ for( uint_t j = i - k + 2; j <= i; ++j )
+ {
+ if( x[0][j] < min_val ) min_val = x[0][j];
+ if( x[0][j] > max_val ) max_val = x[0][j];
+ }
+ y[0][i] = min_val;
+ y[1][i] = max_val;
+ }
+ return 0;
+}
+
diff --git a/lib/missing_bar_count.c b/lib/missing_bar_count.c
new file mode 100644
index 0000000..e6f548e
--- /dev/null
+++ b/lib/missing_bar_count.c
@@ -0,0 +1,51 @@
+// Missing Bar Count
+//
+// missing_bar_count
+//
+// Missing bar counter
+//
+// Counts consecutive missing data bars
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ fp_t threshold;
+} ts_missing_bar_count_params_t;
+
+ts_parameter_t const ts_missing_bar_count_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_missing_bar_count_params_t) };
+
+uint_t ts_missing_bar_count_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_missing_bar_count(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_missing_bar_count_p );
+ if ( !c ) return -1;
+ if (n == 0) return -1;
+ fp_t threshold = ((ts_missing_bar_count_params_t *)c)->threshold;
+
+ y[0][0] = 0;
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ if( fabs( x[0][i] - x[0][i - 1] ) < threshold )
+ y[0][i] = y[0][i - 1] + 1;
+ else
+ y[0][i] = 0;
+ }
+ return 0;
+}
+
diff --git a/lib/momentum.c b/lib/momentum.c
new file mode 100644
index 0000000..f0d89fd
--- /dev/null
+++ b/lib/momentum.c
@@ -0,0 +1,46 @@
+// Momentum
+//
+// momentum
+//
+// Momentum
+//
+// C[t] - C[t-k]
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_momentum_params_t;
+
+ts_parameter_t const ts_momentum_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_momentum_params_t) };
+
+uint_t ts_momentum_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_momentum_params_t *)c)->k : 0 );
+}
+
+int ts_momentum(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_momentum_p );
+ if ( !c ) return -1;
+ TS_CHECK_INPUT(x, x_n, n);
+ uint_t k = ((ts_momentum_params_t *)c)->k;
+
+ for( uint_t i = k; i < n; ++i )
+ {
+ y[0][i] = x[0][i] - x[0][i - k];
+ }
+
+ return 0;
+}
+
diff --git a/lib/money_flow_multiplier.c b/lib/money_flow_multiplier.c
new file mode 100644
index 0000000..f00707c
--- /dev/null
+++ b/lib/money_flow_multiplier.c
@@ -0,0 +1,42 @@
+// Money Flow Multiplier
+//
+// money_flow_multiplier
+//
+// Money Flow Multiplier
+//
+//
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_money_flow_multiplier_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+// ( ( c - l ) - ( h - c ) ) / ( h - l )
+uint_t ts_money_flow_multiplier_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_money_flow_multiplier(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_money_flow_multiplier_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t denom = x[1][i] - x[2][i];
+ y[0][i] = denom != 0 ? ( ( x[3][i] - x[2][i] ) - ( x[1][i] - x[3][i] ) ) / denom : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/moving_linear_regression.c b/lib/moving_linear_regression.c
new file mode 100644
index 0000000..ceb34d5
--- /dev/null
+++ b/lib/moving_linear_regression.c
@@ -0,0 +1,83 @@
+// Moving Linear Regression
+//
+// moving_linear_regression
+//
+// Moving Linear Regression
+//
+// Rolling least-squares regression line
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_moving_linear_regression_params_t;
+
+ts_parameter_t const ts_moving_linear_regression_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_moving_linear_regression_params_t) };
+
+uint_t ts_moving_linear_regression_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_moving_linear_regression_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_moving_linear_regression(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_moving_linear_regression_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_moving_linear_regression_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ fp_t sum_x = 0, sum_x2 = 0;
+ for ( uint_t j = 0; j < k; ++j )
+ {
+ fp_t xv = (fp_t)j;
+ sum_x += xv;
+ sum_x2 += xv * xv;
+ }
+ fp_t denom = (fp_t)k * sum_x2 - sum_x * sum_x;
+ fp_t fk = (fp_t)k;
+
+ fp_t sum_y = 0, sum_xy = 0;
+ for ( uint_t j = 0; j < k; ++j )
+ {
+ fp_t yv = x[0][j];
+ sum_y += yv;
+ sum_xy += (fp_t)j * yv;
+ }
+ if (denom == 0) {
+ y[0][k - 1] = sum_y / fk;
+ } else {
+ fp_t slope = (fk * sum_xy - sum_x * sum_y) / denom;
+ fp_t intercept = (sum_y - slope * sum_x) / fk;
+ fp_t x_last = (fp_t)(k - 1);
+ y[0][k - 1] = intercept + slope * x_last;
+ }
+
+ for ( uint_t i = k; i < n; ++i )
+ {
+ fp_t y_new = x[0][i];
+ fp_t y_old = x[0][i - k];
+ fp_t sum_y_old = sum_y;
+ sum_y = sum_y - y_old + y_new;
+ sum_xy = sum_xy - sum_y_old + y_old + (fk - 1) * y_new;
+ if (denom == 0) {
+ y[0][i] = sum_y / fk;
+ } else {
+ fp_t slope = (fk * sum_xy - sum_x * sum_y) / denom;
+ fp_t intercept = (sum_y - slope * sum_x) / fk;
+ fp_t x_last = (fp_t)(k - 1);
+ y[0][i] = intercept + slope * x_last;
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/moving_linear_regression_r2.c b/lib/moving_linear_regression_r2.c
new file mode 100644
index 0000000..3015a25
--- /dev/null
+++ b/lib/moving_linear_regression_r2.c
@@ -0,0 +1,88 @@
+// Moving Linear Regression R2
+//
+// moving_linear_regression_r2
+//
+// Moving Linear Regression R²
+//
+// Rolling coefficient of determination
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_moving_linear_regression_r2_params_t;
+
+ts_parameter_t const ts_moving_linear_regression_r2_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_moving_linear_regression_r2_params_t) };
+
+uint_t ts_moving_linear_regression_r2_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_moving_linear_regression_r2_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_moving_linear_regression_r2(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_moving_linear_regression_r2_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_moving_linear_regression_r2_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ fp_t sum_x = 0, sum_x2 = 0;
+ for ( uint_t j = 0; j < k; ++j )
+ {
+ fp_t xv = (fp_t)j;
+ sum_x += xv;
+ sum_x2 += xv * xv;
+ }
+ fp_t fk = (fp_t)k;
+ fp_t denom_x = fk * sum_x2 - sum_x * sum_x;
+
+ fp_t sum_y = 0, sum_xy = 0, sum_y2 = 0;
+ for ( uint_t j = 0; j < k; ++j )
+ {
+ fp_t yv = x[0][j];
+ sum_y += yv;
+ sum_xy += (fp_t)j * yv;
+ sum_y2 += yv * yv;
+ }
+ fp_t denom_y = fk * sum_y2 - sum_y * sum_y;
+ fp_t num = fk * sum_xy - sum_x * sum_y;
+ if (denom_x == 0 || denom_y == 0) {
+ y[0][k - 1] = 0;
+ } else {
+ fp_t prod = denom_x * denom_y;
+ fp_t r = prod > 0 ? num / sqrt(prod) : 0;
+ y[0][k - 1] = r * r;
+ }
+
+ for ( uint_t i = k; i < n; ++i )
+ {
+ fp_t y_new = x[0][i];
+ fp_t y_old = x[0][i - k];
+ fp_t sum_y_old = sum_y;
+ sum_y = sum_y - y_old + y_new;
+ sum_xy = sum_xy - sum_y_old + y_old + (fk - 1) * y_new;
+ sum_y2 = sum_y2 - y_old * y_old + y_new * y_new;
+ denom_y = fk * sum_y2 - sum_y * sum_y;
+ num = fk * sum_xy - sum_x * sum_y;
+ if (denom_x == 0 || denom_y == 0) {
+ y[0][i] = 0;
+ } else {
+ fp_t prod = denom_x * denom_y;
+ fp_t r = prod > 0 ? num / sqrt(prod) : 0;
+ y[0][i] = r * r;
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/multiplication.c b/lib/multiplication.c
new file mode 100644
index 0000000..74a7985
--- /dev/null
+++ b/lib/multiplication.c
@@ -0,0 +1,39 @@
+// Multiplication
+//
+// multiplication - mul
+//
+// f( x_0, x_1 ) = x_0 * x_1
+//
+// X_n - 2
+// Y_n - 1
+// C_n - 0
+
+
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_multiplication_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_multiplication_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_multiplication(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_multiplication_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = x[0][i] * x[1][i];
+ }
+ return 0;
+}
+
diff --git a/lib/nan_fn.c b/lib/nan_fn.c
new file mode 100644
index 0000000..cd93069
--- /dev/null
+++ b/lib/nan_fn.c
@@ -0,0 +1,41 @@
+// NaN Function
+//
+// nan_fn - Returns NaN for all inputs
+//
+// y[t] = NaN
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_nan_fn_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_nan_fn_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_nan_fn(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_nan_fn_p );
+ (void)c;
+ ( void ) x;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = NAN;
+ }
+ return 0;
+}
+
diff --git a/lib/natr.c b/lib/natr.c
new file mode 100644
index 0000000..ce58bb1
--- /dev/null
+++ b/lib/natr.c
@@ -0,0 +1,57 @@
+// Natr
+//
+// natr
+//
+// Normalized ATR
+//
+//
+//
+// Inputs: 4
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+} ts_natr_params_t;
+
+ts_parameter_t const ts_natr_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_natr_params_t) };
+
+
+uint_t ts_natr_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_natr_params_t *)c)->k : 0 );
+}
+
+int ts_natr(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_natr_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_natr_params_t *)c)->k;
+ if (k == 0) return -1;
+ if (n < k + 1) return -1;
+
+ for( uint_t i = k; i < n; ++i )
+ {
+ fp_t tr_sum = 0;
+ for( uint_t j = 0; j < k; ++j )
+ {
+ uint_t idx = i - k + 1 + j;
+ fp_t tr = fmax( ts_h( x, idx ) - ts_l( x, idx ), fmax( fabs( ts_h( x, idx ) - ts_c( x, idx - 1 ) ), fabs( ts_l( x, idx ) - ts_c( x, idx - 1 ) ) ) );
+ tr_sum += tr;
+ }
+ fp_t atr = tr_sum / (fp_t)k;
+ y[0][i] = ts_c( x, i ) != 0 ? atr / ts_c( x, i ) * 100.0 : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/nearbyint.c b/lib/nearbyint.c
new file mode 100644
index 0000000..de32777
--- /dev/null
+++ b/lib/nearbyint.c
@@ -0,0 +1,40 @@
+// Nearbyint
+//
+// nearbyint
+//
+// Nearby integer
+//
+// Rounded to nearest integer per current rounding mode
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_nearbyint_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_nearbyint_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_nearbyint(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_nearbyint_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = nearbyint( x[0][i] );
+ }
+ return 0;
+}
+
diff --git a/lib/nextafter.c b/lib/nextafter.c
new file mode 100644
index 0000000..8c766e8
--- /dev/null
+++ b/lib/nextafter.c
@@ -0,0 +1,40 @@
+// Nextafter
+//
+// nextafter
+//
+// Next representable float
+//
+// Next float after x toward y
+//
+// Inputs: 2
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_nextafter_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_nextafter_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_nextafter(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_nextafter_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = nextafter( x[0][i], x[1][i] );
+ }
+ return 0;
+}
+
diff --git a/lib/nexttoward.c b/lib/nexttoward.c
new file mode 100644
index 0000000..740dd91
--- /dev/null
+++ b/lib/nexttoward.c
@@ -0,0 +1,40 @@
+// Nexttoward
+//
+// nexttoward
+//
+// Next representable double
+//
+// Next double after x toward y
+//
+// Inputs: 2
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_nexttoward_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_nexttoward_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_nexttoward(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_nexttoward_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = nexttoward( x[0][i], (long double)x[1][i] );
+ }
+ return 0;
+}
+
diff --git a/lib/none.c b/lib/none.c
new file mode 100644
index 0000000..c9449d5
--- /dev/null
+++ b/lib/none.c
@@ -0,0 +1,36 @@
+// None (Identity)
+//
+// none - identity function
+//
+// Passes input through unchanged: y = x
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+#include <string.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_none_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_none_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_none(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_none_p );
+ (void)c;
+ memcpy( y[0], x[0], sizeof( fp_t ) * n );
+ return 0;
+}
+
diff --git a/lib/nvi.c b/lib/nvi.c
new file mode 100644
index 0000000..f4d3397
--- /dev/null
+++ b/lib/nvi.c
@@ -0,0 +1,51 @@
+// Nvi
+//
+// nvi
+//
+// Negative Volume Index
+//
+// Accumulates price change on declining volume
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_nvi_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_nvi_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_nvi(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_nvi_p );
+ (void)c;
+ if (n == 0) return -1;
+
+ fp_t nvi = 1000;
+
+ y[0][0] = nvi;
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ if( x[4][i] < x[4][i - 1] && x[3][i - 1] != 0 )
+ {
+ fp_t r = ( x[3][i] - x[3][i - 1] ) / x[3][i - 1];
+ nvi = nvi * ( 1 + r );
+ }
+ y[0][i] = nvi;
+ }
+
+ return 0;
+}
+
diff --git a/lib/obv.c b/lib/obv.c
new file mode 100644
index 0000000..3765ba3
--- /dev/null
+++ b/lib/obv.c
@@ -0,0 +1,54 @@
+// Obv
+//
+// obv
+//
+// On-Balance Volume
+//
+//
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_obv_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_obv_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 1 );
+}
+
+int ts_obv(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_obv_p );
+ (void)c;
+ TS_CHECK_INPUT(x, x_n, n);
+ if (n == 0) return -1;
+
+ fp_t obv = 0;
+ y[0][0] = 0;
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ if( x[3][i] > x[3][i - 1] )
+ {
+ obv += x[4][i];
+ }
+ else if( x[3][i] < x[3][i - 1] )
+ {
+ obv -= x[4][i];
+ }
+ y[0][i] = obv;
+ }
+
+ return 0;
+}
+
diff --git a/lib/on_balance_volume_rate.c b/lib/on_balance_volume_rate.c
new file mode 100644
index 0000000..7183c23
--- /dev/null
+++ b/lib/on_balance_volume_rate.c
@@ -0,0 +1,74 @@
+// On Balance Volume Rate
+//
+// on_balance_volume_rate
+//
+// OBV Rate of Change
+//
+//
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 1
+
+
+#include <stdlib.h>
+#include "ts.h"
+
+typedef struct {
+ fp_t f0;
+} ts_on_balance_volume_rate_params_t;
+
+ts_parameter_t const ts_on_balance_volume_rate_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_on_balance_volume_rate_params_t) };
+
+uint_t ts_on_balance_volume_rate_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? (uint_t)((ts_on_balance_volume_rate_params_t *)c)->f0 + 1 : 0 );
+}
+
+int ts_on_balance_volume_rate(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_on_balance_volume_rate_p );
+ if ( !c ) return -1;
+ uint_t k = (uint_t)((ts_on_balance_volume_rate_params_t *)c)->f0;
+ if (k == 0 || k >= n) return -1;
+
+ fp_t obv = 0;
+
+ fp_t *obv_history = NULL;
+ if( n > 0 )
+ {
+ obv_history = calloc( n, sizeof( fp_t ) );
+ if( !obv_history ) return -1;
+ }
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ if( x[3][i] > x[3][i - 1] )
+ {
+ obv += x[4][i];
+ }
+ else if( x[3][i] < x[3][i - 1] )
+ {
+ obv -= x[4][i];
+ }
+ obv_history[i] = obv;
+ }
+
+ for (uint_t i = 0; i <= k; ++i) y[0][i] = 0;
+
+ for( uint_t i = k + 1; i < n; ++i )
+ {
+ fp_t obv_prev = obv_history[i - k];
+ y[0][i] = obv_prev != 0 ? ( obv_history[i] - obv_prev ) / obv_prev * 100 : 0;
+ }
+
+ free( obv_history );
+
+ return 0;
+}
+
diff --git a/lib/open_interest.c b/lib/open_interest.c
new file mode 100644
index 0000000..a3d23aa
--- /dev/null
+++ b/lib/open_interest.c
@@ -0,0 +1,40 @@
+// Open Interest
+//
+// open_interest
+//
+// Open Interest change
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_open_interest_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_open_interest_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_open_interest(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_open_interest_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = x[0][i];
+ }
+
+ return 0;
+}
+
diff --git a/lib/open_minus_close.c b/lib/open_minus_close.c
new file mode 100644
index 0000000..2aaca32
--- /dev/null
+++ b/lib/open_minus_close.c
@@ -0,0 +1,39 @@
+// Open Minus Close
+//
+// open_minus_close
+//
+// Open minus Close
+//
+//
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_open_minus_close_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_open_minus_close_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_open_minus_close(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_open_minus_close_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = x[0][i] - x[3][i];
+ }
+ return 0;
+}
+
diff --git a/lib/oscillator_cross.c b/lib/oscillator_cross.c
new file mode 100644
index 0000000..0c6c5f8
--- /dev/null
+++ b/lib/oscillator_cross.c
@@ -0,0 +1,70 @@
+// Oscillator Cross
+//
+// oscillator_cross
+//
+// Oscillator crossover detector
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 2
+
+
+#include "ts.h"
+
+typedef struct {
+ fp_t fast;
+ fp_t slow;
+} ts_oscillator_cross_params_t;
+
+ts_parameter_t const ts_oscillator_cross_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 2, .params_size = sizeof(ts_oscillator_cross_params_t) };
+
+uint_t ts_oscillator_cross_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? (uint_t)((ts_oscillator_cross_params_t *)c)->slow : 0 );
+}
+
+int ts_oscillator_cross(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_oscillator_cross_p );
+ if ( !c ) return -1;
+ fp_t const fast = ((ts_oscillator_cross_params_t *)c)->fast;
+ fp_t const slow = ((ts_oscillator_cross_params_t *)c)->slow;
+ if (fast == 0 || slow == 0) return -1;
+ if (n == 0) return -1;
+ fp_t const fast_a = 1.0 / fast;
+ fp_t const slow_a = 1.0 / slow;
+ fp_t fast_e = x[0][0];
+ fp_t slow_e = x[0][0];
+ uint_t const offset = (uint_t)slow;
+ if (offset >= n) return -1;
+
+ for( uint_t i = 0; i < offset; ++i )
+ y[0][i] = 0;
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ fp_t pf = fast_e;
+ fp_t ps = slow_e;
+ fast_e = fast_e + fast_a * ( x[0][i] - fast_e );
+ slow_e = slow_e + slow_a * ( x[0][i] - slow_e );
+
+ if( i >= offset )
+ {
+ if( fast_e > slow_e && pf <= ps )
+ y[0][i] = 1;
+ else if( fast_e < slow_e && pf >= ps )
+ y[0][i] = -1;
+ else
+ y[0][i] = 0;
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/osma.c b/lib/osma.c
new file mode 100644
index 0000000..a5a2756
--- /dev/null
+++ b/lib/osma.c
@@ -0,0 +1,78 @@
+// Osma
+//
+// osma
+//
+// Oscillator Moving Average
+//
+//
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 3
+
+
+#include "ts.h"
+
+typedef struct {
+ fp_t fast_k;
+ fp_t slow_k;
+ fp_t signal_k;
+} ts_osma_params_t;
+
+ts_parameter_t const ts_osma_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 3, .params_size = sizeof(ts_osma_params_t) };
+
+uint_t ts_osma_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? (uint_t)((ts_osma_params_t *)c)->slow_k : 0 );
+}
+
+int ts_osma(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_osma_p );
+ if ( !c ) return -1;
+ fp_t fast_k = ((ts_osma_params_t *)c)->fast_k;
+ fp_t slow_k = ((ts_osma_params_t *)c)->slow_k;
+ fp_t signal_k = ((ts_osma_params_t *)c)->signal_k;
+
+ if (fast_k == 0 || slow_k == 0 || signal_k == 0) return -1;
+ if (n == 0) return -1;
+
+ fp_t fast_a = 1.0 / fast_k;
+ fp_t slow_a = 1.0 / slow_k;
+ fp_t signal_a = 1.0 / signal_k;
+
+ fp_t fast_ema = x[0][0];
+ fp_t slow_ema = x[0][0];
+
+ uint_t slow_offset = (uint_t)(slow_k - 1);
+ if (n <= slow_offset + 1) return -1;
+
+ for( uint_t i = 1; i <= slow_offset; ++i )
+ {
+ fast_ema = fast_ema + fast_a * ( x[0][i] - fast_ema );
+ slow_ema = slow_ema + slow_a * ( x[0][i] - slow_ema );
+ }
+
+ fp_t macd_line = fast_ema - slow_ema;
+ fp_t signal_ema = macd_line;
+
+ for( uint_t i = slow_offset + 1; i < n; ++i )
+ {
+ fast_ema = fast_ema + fast_a * ( x[0][i] - fast_ema );
+ slow_ema = slow_ema + slow_a * ( x[0][i] - slow_ema );
+
+ macd_line = fast_ema - slow_ema;
+ signal_ema = signal_ema + signal_a * ( macd_line - signal_ema );
+
+ y[0][i] = macd_line - signal_ema;
+ }
+
+ return 0;
+}
+
+
diff --git a/lib/parabolic_sar.c b/lib/parabolic_sar.c
new file mode 100644
index 0000000..ff84834
--- /dev/null
+++ b/lib/parabolic_sar.c
@@ -0,0 +1,99 @@
+// Parabolic Sar
+//
+// parabolic_sar
+//
+// Parabolic SAR
+//
+//
+//
+// Inputs: 4
+// Outputs: 2
+// Constants: 2
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ fp_t step;
+ fp_t max_af;
+} ts_parabolic_sar_params_t;
+
+ts_parameter_t const ts_parabolic_sar_p = { .n_inputs = 4, .n_outputs = 2, .n_constants = 2, .params_size = sizeof(ts_parabolic_sar_params_t) };
+
+uint_t ts_parabolic_sar_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_parabolic_sar(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_parabolic_sar_p );
+ if ( !c ) return -1;
+ fp_t step = ((ts_parabolic_sar_params_t *)c)->step;
+ fp_t max_af = ((ts_parabolic_sar_params_t *)c)->max_af;
+ if (step <= 0 || max_af <= 0) return -1;
+ if (isnan(step) || isnan(max_af)) return -1;
+ if (step > max_af) return -1;
+ if (n == 0) return -1;
+
+ int_t trend = 1;
+ fp_t sar = ts_l( x, 0 );
+ fp_t ep = ts_h( x, 0 );
+ fp_t af = step;
+
+ for ( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = sar;
+ y[1][i] = (fp_t)(trend > 0);
+
+ if (i == 0) continue;
+
+ if (trend > 0)
+ {
+ sar = sar + af * (ep - sar);
+ if (ts_l( x, i ) < sar)
+ {
+ trend = -1;
+ sar = ep;
+ ep = ts_l( x, i );
+ af = step;
+ }
+ else
+ {
+ if (ts_h( x, i ) > ep)
+ {
+ ep = ts_h( x, i );
+ af = af + step;
+ if (af > max_af) af = max_af;
+ }
+ }
+ }
+ else
+ {
+ sar = sar + af * (ep - sar);
+ if (ts_h( x, i ) > sar)
+ {
+ trend = 1;
+ sar = ep;
+ ep = ts_h( x, i );
+ af = step;
+ }
+ else
+ {
+ if (ts_l( x, i ) < ep)
+ {
+ ep = ts_l( x, i );
+ af = af + step;
+ if (af > max_af) af = max_af;
+ }
+ }
+ }
+ }
+ return 0;
+}
diff --git a/lib/performance_index.c b/lib/performance_index.c
new file mode 100644
index 0000000..c211379
--- /dev/null
+++ b/lib/performance_index.c
@@ -0,0 +1,39 @@
+// Performance Index
+//
+// performance_index
+//
+// Performance Index
+//
+//
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_performance_index_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_performance_index_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_performance_index(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_performance_index_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = x[0][i] != 0 ? ( x[3][i] - x[0][i] ) / x[0][i] * 100 : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/performance_ratio.c b/lib/performance_ratio.c
new file mode 100644
index 0000000..ffd0ece
--- /dev/null
+++ b/lib/performance_ratio.c
@@ -0,0 +1,50 @@
+// Performance Ratio
+//
+// performance_ratio
+//
+// Performance Ratio
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_performance_ratio_params_t;
+
+ts_parameter_t const ts_performance_ratio_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_performance_ratio_params_t) };
+
+uint_t ts_performance_ratio_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_performance_ratio_params_t *)c)->k : 0 );
+}
+
+int ts_performance_ratio(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_performance_ratio_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_performance_ratio_params_t *)c)->k;
+ if (k <= 1 || n == 0) return -1;
+
+ fp_t factor = (fp_t)k / (k - 1);
+
+ for( uint_t i = k; i < n; ++i )
+ {
+ fp_t v = x[0][i - k + 1];
+ fp_t inner = v * v - 1;
+ y[0][i] = inner >= 0 ? sqrt( factor * inner ) : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/pivot_points.c b/lib/pivot_points.c
new file mode 100644
index 0000000..b86c44a
--- /dev/null
+++ b/lib/pivot_points.c
@@ -0,0 +1,51 @@
+// Pivot Points
+//
+// pivot_points
+//
+// Pivot Points
+//
+//
+//
+// Inputs: 5
+// Outputs: 5
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_pivot_points_p = { .n_inputs = 4, .n_outputs = 5, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_pivot_points_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 1 );
+}
+
+int ts_pivot_points(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_pivot_points_p );
+ (void)c;
+ if (n > 0) { y[0][0] = 0; y[1][0] = 0; y[2][0] = 0; y[3][0] = 0; y[4][0] = 0; }
+ for( uint_t i = 1; i < n; ++i )
+ {
+ fp_t prev_h = ts_h( x, i - 1 );
+ fp_t prev_l = ts_l( x, i - 1 );
+ fp_t prev_c = ts_c( x, i - 1 );
+
+ fp_t p = ( prev_h + prev_l + prev_c ) / 3.0;
+ fp_t range = prev_h - prev_l;
+
+ y[0][i] = p;
+ y[1][i] = 2.0 * p - prev_l;
+ y[2][i] = 2.0 * p - prev_h;
+ y[3][i] = p + range;
+ y[4][i] = p - range;
+ }
+ return 0;
+}
+
diff --git a/lib/plot.c b/lib/plot.c
new file mode 100644
index 0000000..f459b05
--- /dev/null
+++ b/lib/plot.c
@@ -0,0 +1,39 @@
+// Plot
+//
+// plot
+//
+// Plot indicator
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_plot_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_plot_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_plot(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_plot_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = x[0][i];
+ }
+ return 0;
+}
+
diff --git a/lib/poisson_pdf.c b/lib/poisson_pdf.c
new file mode 100644
index 0000000..e1e2d59
--- /dev/null
+++ b/lib/poisson_pdf.c
@@ -0,0 +1,48 @@
+// Poisson Pdf
+//
+// poisson_pdf
+//
+// Poisson probability density
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ fp_t lambda;
+} ts_poisson_pdf_params_t;
+
+ts_parameter_t const ts_poisson_pdf_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_poisson_pdf_params_t) };
+
+uint_t ts_poisson_pdf_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_poisson_pdf(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_poisson_pdf_p );
+ if ( !c ) return -1;
+ fp_t lambda = ((ts_poisson_pdf_params_t *)c)->lambda;
+ if (lambda < 0.0 || isnan(lambda) || isinf(lambda)) return -1;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t k_val = x[0][i];
+ if( k_val < 0 ) { y[0][i] = 0; continue; }
+ uint_t k = (uint_t)k_val;
+ y[0][i] = pow( lambda, k ) * exp( -lambda ) / tgamma( k + 1.0 );
+ }
+ return 0;
+}
+
diff --git a/lib/polynomial.c b/lib/polynomial.c
new file mode 100644
index 0000000..2914fe6
--- /dev/null
+++ b/lib/polynomial.c
@@ -0,0 +1,41 @@
+// Polynomial Power
+//
+// polynomial - pow(x, y)
+//
+// x raised to the power y
+//
+// Inputs: 2 (base, exponent)
+// Outputs: 1
+// Constants: 0
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_polynomial_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_polynomial_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_polynomial(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_polynomial_p );
+ (void)c;
+ /* x[0] = base, x[1] = exponent */
+ for (uint_t i = 0; i < n; ++i)
+ {
+ fp_t base = x[0][i];
+ fp_t exponent = x[1][i];
+ if( base < 0.0 && exponent != (fp_t)(long)exponent ) { y[0][i] = 0; continue; }
+ y[0][i] = pow(base, exponent);
+ }
+ return 0;
+}
+
diff --git a/lib/power.c b/lib/power.c
new file mode 100755
index 0000000..41bb644
--- /dev/null
+++ b/lib/power.c
@@ -0,0 +1,71 @@
+// Power
+//
+// power - pow
+//
+// f( x ) = x^r
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 1
+//
+// c[0] = r
+
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ fp_t r;
+} ts_power_params_t;
+
+ts_parameter_t const ts_power_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_power_params_t) };
+
+
+uint_t ts_power_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_power(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_power_p );
+ if ( !c ) return -1;
+ fp_t r = ((ts_power_params_t *)c)->r;
+
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t base = x[0][i];
+ if ( base > 0 )
+ {
+ y[0][i] = pow( base, r );
+ }
+ else if ( base < 0 )
+ {
+ fp_t intpart;
+ if ( modf( r, &intpart ) == 0 )
+ {
+ fp_t result = pow( -base, r );
+ long ip = (long)intpart;
+ y[0][i] = (ip >= 0 && (ip & 1) != 0) ? -result : result;
+ }
+ else
+ {
+ y[0][i] = 0;
+ }
+ }
+ else
+ {
+ if (r > 0.0) y[0][i] = 0.0;
+ else if (r < 0.0) y[0][i] = INFINITY;
+ else y[0][i] = 1.0;
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/ppo.c b/lib/ppo.c
new file mode 100644
index 0000000..b2b3bbf
--- /dev/null
+++ b/lib/ppo.c
@@ -0,0 +1,94 @@
+// Ppo
+//
+// ppo
+//
+// Percentage Price Oscillator
+//
+//
+//
+// Inputs: 5
+// Outputs: 2
+// Constants: 3
+
+
+#include "ts.h"
+
+typedef struct {
+ fp_t fast_k;
+ fp_t slow_k;
+ fp_t signal_k;
+} ts_ppo_params_t;
+
+ts_parameter_t const ts_ppo_p = { .n_inputs = 5, .n_outputs = 2, .n_constants = 3, .params_size = sizeof(ts_ppo_params_t) };
+
+uint_t ts_ppo_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? (uint_t)(((ts_ppo_params_t *)c)->slow_k + ((ts_ppo_params_t *)c)->signal_k - 2) : 0 );
+}
+
+int ts_ppo(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_ppo_p );
+ if ( !c ) return -1;
+ fp_t fast_k = ((ts_ppo_params_t *)c)->fast_k;
+ fp_t slow_k = ((ts_ppo_params_t *)c)->slow_k;
+ fp_t signal_k = ((ts_ppo_params_t *)c)->signal_k;
+
+ if (fast_k == 0 || slow_k == 0 || signal_k == 0) return -1;
+ if (n == 0) return -1;
+
+ fp_t fast_a = 1.0 / fast_k;
+ fp_t slow_a = 1.0 / slow_k;
+ fp_t signal_a = 1.0 / signal_k;
+
+ fp_t fast_ema = x[0][0];
+ fp_t slow_ema = x[0][0];
+
+ uint_t slow_offset = (uint_t)(slow_k - 1);
+ uint_t signal_offset = (uint_t)(signal_k - 1);
+ uint_t total_offset = slow_offset + signal_offset;
+
+ if (n <= total_offset) return -1;
+
+ for( uint_t i = 1; i <= slow_offset; ++i )
+ {
+ fast_ema = fast_ema + fast_a * ( x[0][i] - fast_ema );
+ slow_ema = slow_ema + slow_a * ( x[0][i] - slow_ema );
+ }
+
+ fp_t ppo_val = slow_ema != 0 ? (fast_ema - slow_ema) / slow_ema * 100.0 : 0;
+ fp_t signal_ema = ppo_val;
+
+ for( uint_t i = slow_offset + 1; i <= total_offset; ++i )
+ {
+ fast_ema = fast_ema + fast_a * ( x[0][i] - fast_ema );
+ slow_ema = slow_ema + slow_a * ( x[0][i] - slow_ema );
+
+ ppo_val = slow_ema != 0 ? (fast_ema - slow_ema) / slow_ema * 100.0 : 0;
+ signal_ema = signal_ema + signal_a * ( ppo_val - signal_ema );
+ }
+
+ y[0][total_offset] = ppo_val;
+ y[1][total_offset] = signal_ema;
+
+ for( uint_t i = total_offset + 1; i < n; ++i )
+ {
+ fast_ema = fast_ema + fast_a * ( x[0][i] - fast_ema );
+ slow_ema = slow_ema + slow_a * ( x[0][i] - slow_ema );
+
+ ppo_val = slow_ema != 0 ? (fast_ema - slow_ema) / slow_ema * 100.0 : 0;
+ signal_ema = signal_ema + signal_a * ( ppo_val - signal_ema );
+
+ y[0][i] = ppo_val;
+ y[1][i] = signal_ema;
+ }
+
+ return 0;
+}
+
+
diff --git a/lib/pretty_good_oscillator.c b/lib/pretty_good_oscillator.c
new file mode 100644
index 0000000..65a05c2
--- /dev/null
+++ b/lib/pretty_good_oscillator.c
@@ -0,0 +1,64 @@
+// Pretty Good Oscillator
+//
+// pretty_good_oscillator
+//
+// Pretty Good Oscillator
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_pretty_good_oscillator_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_pretty_good_oscillator_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_pretty_good_oscillator(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_pretty_good_oscillator_p );
+ (void)c;
+ if (n == 0) return -1;
+ fp_t ema14 = x[0][0];
+ fp_t ema30 = x[0][0];
+ fp_t ema90 = x[0][0];
+ fp_t a14 = 15.0 / (19.0 * 14);
+ fp_t a30 = 15.0 / (19.0 * 30);
+ fp_t a90 = 15.0 / (19.0 * 90);
+
+ for( uint_t i = 0; i < n; ++i )
+ {
+ if( i > 0 )
+ {
+ ema14 = ema14 + a14 * (x[0][i] - ema14);
+ ema30 = ema30 + a30 * (x[0][i] - ema30);
+ ema90 = ema90 + a90 * (x[0][i] - ema90);
+ }
+
+ fp_t arr[3] = {ema14, ema30, ema90};
+ for( uint_t a = 0; a < 2; ++a )
+ for( uint_t b = a + 1; b < 3; ++b )
+ if( arr[a] > arr[b] ) { fp_t t = arr[a]; arr[a] = arr[b]; arr[b] = t; }
+
+ fp_t median = arr[1];
+
+ if( median == 0 )
+ y[0][i] = 0;
+ else
+ y[0][i] = (x[0][i] / median - 1.0);
+ }
+ return 0;
+}
+
diff --git a/lib/price_channel.c b/lib/price_channel.c
new file mode 100644
index 0000000..9aba070
--- /dev/null
+++ b/lib/price_channel.c
@@ -0,0 +1,58 @@
+// Price Channel
+//
+// price_channel
+//
+// Price Channel
+//
+//
+//
+// Inputs: 1
+// Outputs: 3
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_price_channel_params_t;
+
+ts_parameter_t const ts_price_channel_p = { .n_inputs = 1, .n_outputs = 3, .n_constants = 1, .params_size = sizeof(ts_price_channel_params_t) };
+
+uint_t ts_price_channel_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_price_channel_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_price_channel(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_price_channel_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_price_channel_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t hh = x[0][i - k + 1];
+ fp_t ll = x[0][i - k + 1];
+ fp_t sum = x[0][i - k + 1];
+
+ for( uint_t j = i - k + 2; j <= i; ++j )
+ {
+ if( x[0][j] > hh ) hh = x[0][j];
+ if( x[0][j] < ll ) ll = x[0][j];
+ sum += x[0][j];
+ }
+
+ y[0][i] = hh;
+ y[1][i] = sum / (fp_t)k;
+ y[2][i] = ll;
+ }
+ return 0;
+}
+
diff --git a/lib/price_momentum_oscillator.c b/lib/price_momentum_oscillator.c
new file mode 100644
index 0000000..b03f350
--- /dev/null
+++ b/lib/price_momentum_oscillator.c
@@ -0,0 +1,45 @@
+// Price Momentum Oscillator
+//
+// price_momentum_oscillator
+//
+// Price Momentum Oscillator
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_price_momentum_oscillator_params_t;
+
+ts_parameter_t const ts_price_momentum_oscillator_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_price_momentum_oscillator_params_t) };
+
+uint_t ts_price_momentum_oscillator_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_price_momentum_oscillator_params_t *)c)->k : 0 );
+}
+
+int ts_price_momentum_oscillator(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_price_momentum_oscillator_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_price_momentum_oscillator_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for (uint_t i = k; i < n; ++i)
+ {
+ y[0][i] = x[0][i - k] != 0 ? ( x[0][i] / x[0][i - k] - 1 ) * 100 : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/price_rate_of_change.c b/lib/price_rate_of_change.c
new file mode 100644
index 0000000..8f26548
--- /dev/null
+++ b/lib/price_rate_of_change.c
@@ -0,0 +1,45 @@
+// Price Rate Of Change
+//
+// price_rate_of_change
+//
+// Price Rate of Change
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_price_rate_of_change_params_t;
+
+ts_parameter_t const ts_price_rate_of_change_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_price_rate_of_change_params_t) };
+
+uint_t ts_price_rate_of_change_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_price_rate_of_change_params_t *)c)->k : 0 );
+}
+
+int ts_price_rate_of_change(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_price_rate_of_change_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_price_rate_of_change_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for (uint_t i = k; i < n; ++i)
+ {
+ y[0][i] = x[0][i - k] != 0 ? (x[0][i] - x[0][i - k]) / x[0][i - k] * 100 : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/prime_number_bands.c b/lib/prime_number_bands.c
new file mode 100644
index 0000000..ac94e7b
--- /dev/null
+++ b/lib/prime_number_bands.c
@@ -0,0 +1,58 @@
+// Prime Number Bands
+//
+// prime_number_bands
+//
+// Prime Number Bands
+//
+//
+//
+// Inputs: 1
+// Outputs: 3
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_prime_number_bands_params_t;
+
+ts_parameter_t const ts_prime_number_bands_p = { .n_inputs = 1, .n_outputs = 3, .n_constants = 1, .params_size = sizeof(ts_prime_number_bands_params_t) };
+
+uint_t ts_prime_number_bands_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_prime_number_bands_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_prime_number_bands(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_prime_number_bands_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_prime_number_bands_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t sum = 0, sum2 = 0;
+ for( uint_t j = 0; j < k; ++j )
+ {
+ fp_t v = x[0][i - k + 1 + j];
+ sum += v;
+ sum2 += v * v;
+ }
+ fp_t mean = sum / k;
+ fp_t variance = sum2 / k - mean * mean;
+ fp_t stddev = variance >= 0 ? sqrt( variance ) : 0;
+ y[0][i] = mean;
+ y[1][i] = mean + 2.0 * stddev;
+ y[2][i] = mean - 2.0 * stddev;
+ }
+ return 0;
+}
+
diff --git a/lib/prime_number_oscillator.c b/lib/prime_number_oscillator.c
new file mode 100644
index 0000000..a86fa7b
--- /dev/null
+++ b/lib/prime_number_oscillator.c
@@ -0,0 +1,82 @@
+// Prime Number Oscillator
+//
+// prime_number_oscillator
+//
+// Prime Number Oscillator
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_prime_number_oscillator_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_prime_number_oscillator_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 23 );
+}
+
+int ts_prime_number_oscillator(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_prime_number_oscillator_p );
+ (void)c;
+ fp_t last_gap = 0;
+ fp_t prev_prime = 0;
+ bool first_prime = true;
+
+ for( uint_t i = 0; i < n; ++i )
+ {
+ long v = (long)x[0][i];
+ bool is_p = false;
+
+ if( v >= 2 )
+ {
+ if( v == 2 )
+ is_p = true;
+ else if( v % 2 != 0 )
+ {
+ is_p = true;
+ for( long d = 3; d <= v / d; d += 2 )
+ {
+ if( v % d == 0 )
+ {
+ is_p = false;
+ break;
+ }
+ }
+ }
+ }
+
+ if( is_p )
+ {
+ if( first_prime )
+ {
+ prev_prime = (fp_t)v;
+ first_prime = false;
+ y[0][i] = 0;
+ }
+ else
+ {
+ last_gap = (fp_t)v - prev_prime;
+ prev_prime = (fp_t)v;
+ y[0][i] = last_gap;
+ }
+ }
+ else
+ {
+ y[0][i] = last_gap;
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/prings_kst.c b/lib/prings_kst.c
new file mode 100644
index 0000000..e33688e
--- /dev/null
+++ b/lib/prings_kst.c
@@ -0,0 +1,79 @@
+// Prings Kst
+//
+// prings_kst
+//
+// Prings KST
+//
+//
+//
+// Inputs: 1
+// Outputs: 2
+// Constants: 0
+
+
+#include <stdlib.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_prings_kst_p = { .n_inputs = 1, .n_outputs = 2, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_prings_kst_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 8 );
+}
+
+int ts_prings_kst(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_prings_kst_p );
+ (void)c;
+ if (n < 23) return -1;
+
+ fp_t *raw_kst = calloc( n, sizeof( fp_t ) );
+ if ( !raw_kst ) return -1;
+
+ for (uint_t i = 0; i < 8; ++i) { y[0][i] = 0; y[1][i] = 0; }
+
+ for( uint_t i = 8; i < n; ++i )
+ {
+ if ( x[0][i - 2] == 0 || x[0][i - 3] == 0 || x[0][i - 5] == 0 || x[0][i - 8] == 0 )
+ {
+ raw_kst[i] = 0;
+ continue;
+ }
+ fp_t r2 = ( x[0][i] - x[0][i - 2] ) / x[0][i - 2] * 100;
+ fp_t r3 = ( x[0][i] - x[0][i - 3] ) / x[0][i - 3] * 100;
+ fp_t r5 = ( x[0][i] - x[0][i - 5] ) / x[0][i - 5] * 100;
+ fp_t r8 = ( x[0][i] - x[0][i - 8] ) / x[0][i - 8] * 100;
+ raw_kst[i] = r2 + 2 * r3 + 3 * r5 + 4 * r8;
+ }
+
+ fp_t alpha1 = 2.0 / 11.0;
+ y[0][8] = raw_kst[8];
+ for( uint_t i = 9; i < n; ++i )
+ {
+ y[0][i] = y[0][i - 1] + alpha1 * ( raw_kst[i] - y[0][i - 1] );
+ }
+
+ fp_t alpha2 = 2.0 / 16.0;
+ for( uint_t i = 8; i < 16; ++i )
+ y[1][i] = 0;
+ {
+ fp_t seed = 0;
+ for( uint_t i = 8; i <= 16; ++i ) seed += y[0][i];
+ seed /= 9.0;
+ y[1][16] = seed;
+ }
+ for( uint_t i = 17; i < n; ++i )
+ {
+ y[1][i] = y[1][i - 1] + alpha2 * ( y[0][i] - y[1][i - 1] );
+ }
+
+ free( raw_kst );
+ return 0;
+}
+
diff --git a/lib/prings_special_k.c b/lib/prings_special_k.c
new file mode 100644
index 0000000..91a40ba
--- /dev/null
+++ b/lib/prings_special_k.c
@@ -0,0 +1,86 @@
+// Prings Special K
+//
+// prings_special_k
+//
+// Prings Special K
+//
+//
+//
+// Inputs: 4
+// Outputs: 2
+// Constants: 2
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k1;
+ uint_t k2;
+} ts_prings_special_k_params_t;
+
+ts_parameter_t const ts_prings_special_k_p = { .n_inputs = 4, .n_outputs = 2, .n_constants = 2, .params_size = sizeof(ts_prings_special_k_params_t) };
+
+uint_t ts_prings_special_k_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_prings_special_k_params_t *)c)->k1 > 0 && ((ts_prings_special_k_params_t *)c)->k2 > 0 ? ((ts_prings_special_k_params_t *)c)->k1 + ((ts_prings_special_k_params_t *)c)->k2 - 2 : 0 : 0 );
+}
+
+int ts_prings_special_k(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_prings_special_k_p );
+ if ( !c ) return -1;
+ uint_t k1 = ((ts_prings_special_k_params_t *)c)->k1;
+ uint_t k2 = ((ts_prings_special_k_params_t *)c)->k2;
+ if (k1 == 0 || k2 == 0) return -1;
+
+ fp_t ema = 0;
+ fp_t ema_a = 1.0 / (fp_t)k2;
+
+ for (uint_t j = 0; j < n && j < k1 - 1; ++j) { y[0][j] = 0; y[1][j] = 0; }
+ for( uint_t i = k1 - 1; i < n; ++i )
+ {
+ fp_t ll = ts_l( x, i );
+ for( uint_t j = 1; j < k1; ++j )
+ {
+ fp_t v = ts_l( x, i - j );
+ if( v < ll ) ll = v;
+ }
+
+ fp_t hh = ts_h( x, i );
+ for( uint_t j = 1; j < k1; ++j )
+ {
+ fp_t v = ts_h( x, i - j );
+ if( v > hh ) hh = v;
+ }
+
+ fp_t raw = ( hh != ll ) ? 100.0 * ( ts_c( x, i ) - ll ) / ( hh - ll ) : 0;
+
+ if( i == k1 - 1 )
+ {
+ ema = raw;
+ }
+ else
+ {
+ ema = ema + ema_a * ( raw - ema );
+ }
+
+ y[0][i] = ema;
+
+ if( i >= k1 + k2 - 2 )
+ {
+ fp_t d_sum = 0;
+ for( uint_t j = 0; j < k2; ++j )
+ {
+ d_sum += y[0][i - j];
+ }
+ y[1][i] = d_sum / (fp_t)k2;
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/psychological_line.c b/lib/psychological_line.c
new file mode 100644
index 0000000..35fd912
--- /dev/null
+++ b/lib/psychological_line.c
@@ -0,0 +1,56 @@
+// Psychological Line
+//
+// psychological_line
+//
+// Psychological Line
+//
+//
+//
+// Inputs: 4
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_psychological_line_params_t;
+
+ts_parameter_t const ts_psychological_line_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_psychological_line_params_t) };
+
+uint_t ts_psychological_line_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_psychological_line_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_psychological_line(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_psychological_line_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_psychological_line_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ fp_t sum = 0;
+
+ for( uint_t i = 0; i < k; ++i )
+ {
+ sum += ( ts_c( x, i ) > ts_o( x, i ) ) ? 100.0 : 0.0;
+ }
+
+ y[0][k - 1] = sum / (fp_t)k;
+
+ for( uint_t i = k; i < n; ++i )
+ {
+ fp_t s = ( ts_c( x, i ) > ts_o( x, i ) ) ? 100.0 : 0.0;
+ fp_t s_old = ( ts_c( x, i - k ) > ts_o( x, i - k ) ) ? 100.0 : 0.0;
+ y[0][i] = y[0][i - 1] + ( s - s_old ) / (fp_t)k;
+ }
+ return 0;
+}
+
diff --git a/lib/pvi.c b/lib/pvi.c
new file mode 100644
index 0000000..26265d5
--- /dev/null
+++ b/lib/pvi.c
@@ -0,0 +1,61 @@
+// Pvi
+//
+// pvi
+//
+// Positive Volume Index
+//
+//
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_pvi_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_pvi_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_pvi(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_pvi_p );
+ (void)c;
+ fp_t pvi = 1000;
+
+ if (n == 0) return -1;
+
+ y[0][0] = pvi;
+
+ if( n > 1 )
+ {
+ if( x[4][1] > x[4][0] && x[3][0] != 0 )
+ {
+ fp_t r = ( x[3][1] - x[3][0] ) / x[3][0];
+ pvi = pvi * ( 1 + r );
+ }
+ y[0][1] = pvi;
+ }
+
+ for( uint_t i = 2; i < n; ++i )
+ {
+ if( x[4][i] > x[4][i - 1] && x[3][i - 1] != 0 )
+ {
+ fp_t r = ( x[3][i] - x[3][i - 1] ) / x[3][i - 1];
+ pvi = pvi * ( 1 + r );
+ }
+ y[0][i] = pvi;
+ }
+
+ return 0;
+}
+
diff --git a/lib/pvt.c b/lib/pvt.c
new file mode 100644
index 0000000..f98c288
--- /dev/null
+++ b/lib/pvt.c
@@ -0,0 +1,46 @@
+// Pvt
+//
+// pvt
+//
+// Price Volume Trend
+//
+//
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_pvt_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_pvt_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 1 );
+}
+
+int ts_pvt(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_pvt_p );
+ (void)c;
+ if (n > 0) y[0][0] = 0;
+
+ fp_t pvt = 0;
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ if( x[3][i - 1] != 0 )
+ pvt += ( x[3][i] / x[3][i - 1] - 1 ) * x[4][i];
+ y[0][i] = pvt;
+ }
+
+ return 0;
+}
+
diff --git a/lib/qstick.c b/lib/qstick.c
new file mode 100644
index 0000000..d2f4361
--- /dev/null
+++ b/lib/qstick.c
@@ -0,0 +1,50 @@
+// Qstick
+//
+// qstick
+//
+// Q Stick
+//
+//
+//
+// Inputs: 4
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_qstick_params_t;
+
+ts_parameter_t const ts_qstick_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_qstick_params_t) };
+
+uint_t ts_qstick_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_qstick_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_qstick(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_qstick_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_qstick_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t sum = 0;
+ for( uint_t j = 0; j < k; ++j )
+ {
+ sum += ts_c( x, i - k + 1 + j ) - ts_o( x, i - k + 1 + j );
+ }
+ y[0][i] = sum / (fp_t)k;
+ }
+ return 0;
+}
+
diff --git a/lib/radian_degree.c b/lib/radian_degree.c
new file mode 100644
index 0000000..046762a
--- /dev/null
+++ b/lib/radian_degree.c
@@ -0,0 +1,39 @@
+// Radian Degree
+//
+// radian_degree
+//
+// Radians to Degrees
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_radian_degree_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_radian_degree_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_radian_degree(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_radian_degree_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = x[0][i] * 180.0 / PI;
+ }
+ return 0;
+}
+
diff --git a/lib/rainbow_ma.c b/lib/rainbow_ma.c
new file mode 100644
index 0000000..3311ec9
--- /dev/null
+++ b/lib/rainbow_ma.c
@@ -0,0 +1,61 @@
+// Rainbow Ma
+//
+// rainbow_ma
+//
+// Rainbow Moving Average
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_rainbow_ma_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_rainbow_ma_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 19 );
+}
+
+int ts_rainbow_ma(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_rainbow_ma_p );
+ (void)c;
+ uint_t periods[7] = {3, 5, 7, 9, 11, 13, 15};
+
+ for( uint_t j = 0; j < n && j < 19; ++j ) y[0][j] = 0;
+ for( uint_t i = 19; i < n; ++i )
+ {
+ fp_t sma_values[7];
+
+ for( uint_t p = 0; p < 7; ++p )
+ {
+ fp_t sum = 0;
+ for( uint_t j = 0; j < periods[p]; ++j )
+ sum += x[0][i - j];
+ sma_values[p] = sum / periods[p];
+ }
+
+ for( uint_t a = 0; a < 6; ++a )
+ for( uint_t b = a + 1; b < 7; ++b )
+ if( sma_values[a] > sma_values[b] )
+ {
+ fp_t t = sma_values[a];
+ sma_values[a] = sma_values[b];
+ sma_values[b] = t;
+ }
+
+ y[0][i] = sma_values[2];
+ }
+ return 0;
+}
+
diff --git a/lib/rainbow_oscillator.c b/lib/rainbow_oscillator.c
new file mode 100644
index 0000000..1a56b55
--- /dev/null
+++ b/lib/rainbow_oscillator.c
@@ -0,0 +1,68 @@
+// Rainbow Oscillator
+//
+// rainbow_oscillator
+//
+// Rainbow Oscillator
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_rainbow_oscillator_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_rainbow_oscillator_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 13 );
+}
+
+int ts_rainbow_oscillator(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_rainbow_oscillator_p );
+ (void)c;
+ uint_t const periods[] = { 2, 4, 6, 8, 10, 12, 14 };
+ uint_t const num_periods = 7;
+
+ for( uint_t j = 0; j < n && j < 13; ++j ) y[0][j] = 0;
+ for( uint_t i = 13; i < n; ++i )
+ {
+ fp_t ma_values[7];
+
+ for( uint_t p = 0; p < num_periods; ++p )
+ {
+ fp_t sum = 0;
+ for( uint_t j = 0; j < periods[p]; ++j )
+ {
+ sum += x[0][i - j];
+ }
+ ma_values[p] = sum / periods[p];
+ }
+
+ fp_t min_ma = ma_values[0];
+ fp_t max_ma = ma_values[0];
+ for( uint_t p = 1; p < num_periods; ++p )
+ {
+ if( ma_values[p] < min_ma )
+ min_ma = ma_values[p];
+ if( ma_values[p] > max_ma )
+ max_ma = ma_values[p];
+ }
+
+ if( max_ma == min_ma )
+ y[0][i] = 0;
+ else
+ y[0][i] = ( x[0][i] - min_ma ) / ( max_ma - min_ma ) * 100.0;
+ }
+ return 0;
+}
+
diff --git a/lib/random_walk_index.c b/lib/random_walk_index.c
new file mode 100644
index 0000000..b4375e5
--- /dev/null
+++ b/lib/random_walk_index.c
@@ -0,0 +1,64 @@
+// Random Walk Index
+//
+// random_walk_index
+//
+// Random Walk Index
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_random_walk_index_params_t;
+
+ts_parameter_t const ts_random_walk_index_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_random_walk_index_params_t) };
+
+uint_t ts_random_walk_index_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_random_walk_index_params_t *)c)->k : 0 );
+}
+
+int ts_random_walk_index(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_random_walk_index_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_random_walk_index_params_t *)c)->k;
+ if (k == 0 || n < k + 1) return -1;
+
+ fp_t sum_abs = 0;
+ fp_t sum_net = 0;
+
+ for ( uint_t i = 0; i < k; ++i )
+ {
+ fp_t change = x[0][i + 1] - x[0][i];
+ sum_abs += fabs( change );
+ sum_net += change;
+ }
+
+ y[0][k] = sum_net == 0 ? 0 : sum_abs / sum_net;
+
+ for ( uint_t i = k + 1; i < n; ++i )
+ {
+ fp_t change_new = x[0][i] - x[0][i - 1];
+ fp_t change_old = x[0][i - k] - x[0][i - k - 1];
+
+ sum_abs = sum_abs - fabs( change_old ) + fabs( change_new );
+ sum_net = sum_net - change_old + change_new;
+
+ y[0][i] = sum_net == 0 ? 0 : sum_abs / sum_net;
+ }
+ return 0;
+}
+
diff --git a/lib/rank.c b/lib/rank.c
new file mode 100644
index 0000000..392b63f
--- /dev/null
+++ b/lib/rank.c
@@ -0,0 +1,54 @@
+// Rank
+//
+// rank
+//
+// Percentile rank
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_rank_params_t;
+
+ts_parameter_t const ts_rank_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_rank_params_t) };
+
+uint_t ts_rank_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_rank_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_rank(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_rank_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_rank_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for( uint_t i = k - 1; i < n; ++i )
+ {
+ uint_t count = 0;
+ fp_t current = x[0][i];
+
+ for( uint_t j = i - k + 1; j <= i; ++j )
+ {
+ if( x[0][j] <= current )
+ count++;
+ }
+
+ y[0][i] = (fp_t)count / (fp_t)k * 100.0;
+ }
+ return 0;
+}
+
diff --git a/lib/ratio.c b/lib/ratio.c
new file mode 100755
index 0000000..8568476
--- /dev/null
+++ b/lib/ratio.c
@@ -0,0 +1,49 @@
+// Ratio
+//
+// ratio - ratio
+//
+// Calculates the ratio between the current value and the value k samples ago
+//
+// y_i = x_i / x_i-k
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+} ts_ratio_params_t;
+
+ts_parameter_t const ts_ratio_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_ratio_params_t) };
+
+
+uint_t ts_ratio_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_ratio_params_t *)c)->k : 0 );
+}
+
+int ts_ratio(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_ratio_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_ratio_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ uint_t xi = k;
+
+ for( ; xi < n; xi++ )
+ {
+ if( x[0][xi - k] == 0 ) { y[0][xi] = 0; } else { y[0][xi] = x[0][xi] / x[0][xi - k]; }
+ }
+ return 0;
+}
+
diff --git a/lib/ravi.c b/lib/ravi.c
new file mode 100644
index 0000000..fcb3095
--- /dev/null
+++ b/lib/ravi.c
@@ -0,0 +1,88 @@
+// Ravi
+//
+// ravi
+//
+// Rogers Average True Range
+//
+//
+//
+// Inputs: 4
+// Outputs: 2
+// Constants: 2
+
+
+#include <math.h>
+#include <stdlib.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+ fp_t k2;
+} ts_ravi_params_t;
+
+ts_parameter_t const ts_ravi_p = { .n_inputs = 4, .n_outputs = 2, .n_constants = 2, .params_size = sizeof(ts_ravi_params_t) };
+
+uint_t ts_ravi_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_ravi_params_t *)c)->k + (uint_t)((ts_ravi_params_t *)c)->k2 - 2 : 0 );
+}
+
+int ts_ravi(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_ravi_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_ravi_params_t *)c)->k;
+ fp_t k2 = ((ts_ravi_params_t *)c)->k2;
+
+ if( k == 0 || k2 == 0 ) return -1;
+
+ fp_t *num_buf = calloc( n, sizeof( fp_t ) );
+ fp_t *den_buf = calloc( n, sizeof( fp_t ) );
+ if( !num_buf || !den_buf ) { free( num_buf ); free( den_buf ); return -1; }
+
+ for( uint_t i = 0; i < n; ++i )
+ {
+ num_buf[i] = (x[3][i] - x[0][i]) + (x[3][i] - (i > 0 ? x[3][i - 1] : x[3][i]));
+ den_buf[i] = fabs(x[1][i] - x[2][i]) + fabs(x[3][i] - x[0][i]);
+ }
+
+ fp_t num_sum = 0;
+ fp_t den_sum = 0;
+
+ for( uint_t i = 0; i < k; ++i )
+ {
+ num_sum += num_buf[i];
+ den_sum += den_buf[i];
+ }
+
+ fp_t rvi = ( den_sum != 0 ) ? num_sum / den_sum : 0;
+ fp_t sig = rvi;
+ fp_t sig_a = 1.0 / k2;
+ uint_t offset = k + (uint_t)k2 - 2;
+
+ for (uint_t i = 0; i < offset; ++i) { y[0][i] = 0; y[1][i] = 0; }
+
+ for( uint_t i = k; i < n; ++i )
+ {
+ num_sum = num_sum - num_buf[i - k] + num_buf[i];
+ den_sum = den_sum - den_buf[i - k] + den_buf[i];
+ rvi = ( den_sum != 0 ) ? num_sum / den_sum : 0;
+ sig = sig + sig_a * (rvi - sig);
+
+ if( i >= offset )
+ {
+ y[0][i] = rvi;
+ y[1][i] = sig;
+ }
+ }
+
+ free( num_buf );
+ free( den_buf );
+ return 0;
+}
+
diff --git a/lib/reciprocal.c b/lib/reciprocal.c
new file mode 100644
index 0000000..7a42482
--- /dev/null
+++ b/lib/reciprocal.c
@@ -0,0 +1,39 @@
+// Reciprocal
+//
+// reciprocal - rec
+//
+// f( x ) = 1 / x
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_reciprocal_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_reciprocal_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_reciprocal(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_reciprocal_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = _ts_reciprocal( x[0][i] );
+ }
+ return 0;
+}
+
diff --git a/lib/regression_linear_least_squares.c b/lib/regression_linear_least_squares.c
new file mode 100755
index 0000000..49123bb
--- /dev/null
+++ b/lib/regression_linear_least_squares.c
@@ -0,0 +1,92 @@
+// Simple Least Squares Moving Linear Regression
+//
+// regression_linear_simple_least_squares - slrls
+//
+// ((k * sum_{i=0}^{n}[x_i * y_i]) - (sum_{i=0}^{n}[x_i] * sum_{i=0}^{n}[y_i]))
+// β = ────────────────────────────────────────────────────────────────────────
+// ((k * sum_{i=0}^{n}[x_i^2]) - (sum_{i=0}^{n}[x_i^2])^2)
+//
+// α = E(y) - β * E(x)
+// = μ_y - β * μ_x
+//
+// ( E( xy ) - E( μ_x ) * E( μ_y ) )
+// r_xy = ────────────────────────────────────────────────────────────────────────
+// ((E(x^2) - E(x)^2) - (E(y^2) - E(y)^2))^(1 / 2)
+//
+// y = α + βx
+//
+// Inputs: 2
+// Outputs: 3
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+} ts_regression_linear_least_squares_params_t;
+
+ts_parameter_t const ts_regression_linear_least_squares_p = { .n_inputs = 2, .n_outputs = 3, .n_constants = 1, .params_size = sizeof(ts_regression_linear_least_squares_params_t) };
+
+
+uint_t ts_regression_linear_least_squares_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_regression_linear_least_squares_params_t *)c)->k : 0 );
+}
+
+int ts_regression_linear_least_squares(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_regression_linear_least_squares_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_regression_linear_least_squares_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ uint_t xi = k;
+ uint_t yi = k;
+ fp_t var_x, var_y, cov_xy;
+
+ for( ; xi < n; ++xi, ++yi )
+ {
+ // Two-pass variance/covariance for numerical stability
+ fp_t mean_x = 0, mean_y = 0;
+ for (uint_t j = xi - k + 1; j <= xi; j++) {
+ mean_x += x[0][j];
+ mean_y += x[1][j];
+ }
+ mean_x /= (fp_t)k;
+ mean_y /= (fp_t)k;
+ var_x = 0; var_y = 0; cov_xy = 0;
+ for (uint_t j = xi - k + 1; j <= xi; j++) {
+ fp_t dx = x[0][j] - mean_x;
+ fp_t dy = x[1][j] - mean_y;
+ var_x += dx * dx;
+ var_y += dy * dy;
+ cov_xy += dx * dy;
+ }
+ var_x /= (fp_t)k;
+ var_y /= (fp_t)k;
+ cov_xy /= (fp_t)k;
+
+ if ( var_x != 0 )
+ {
+ y[0][yi] = cov_xy / var_x;
+ y[1][yi] = mean_y - y[0][yi] * mean_x;
+ }
+ else
+ {
+ y[0][yi] = 0;
+ y[1][yi] = mean_y;
+ }
+ fp_t prod = var_x * var_y;
+ y[2][yi] = prod > 0 ? cov_xy / sqrt( prod ) : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/relative_strength.c b/lib/relative_strength.c
new file mode 100644
index 0000000..634cd47
--- /dev/null
+++ b/lib/relative_strength.c
@@ -0,0 +1,41 @@
+// Relative Strength
+//
+// relative_strength
+//
+// Relative Strength
+//
+//
+//
+// Inputs: 2
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_relative_strength_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_relative_strength_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_relative_strength(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_relative_strength_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = x[1][i] == 0.0 ? 0.0 : x[0][i] / x[1][i] * 100.0;
+ }
+ return 0;
+}
+
diff --git a/lib/relative_volatility.c b/lib/relative_volatility.c
new file mode 100644
index 0000000..9682a2d
--- /dev/null
+++ b/lib/relative_volatility.c
@@ -0,0 +1,75 @@
+// Relative Volatility
+//
+// relative_volatility
+//
+// Relative Volatility Index
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_relative_volatility_params_t;
+
+ts_parameter_t const ts_relative_volatility_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_relative_volatility_params_t) };
+
+uint_t ts_relative_volatility_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_relative_volatility_params_t *)c)->k - 1 : 0 );
+}
+
+int ts_relative_volatility(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_relative_volatility_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_relative_volatility_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ uint_t xi = 0;
+ uint_t yi = k - 1;
+
+ fp_t sum = 0;
+ fp_t sum2 = 0;
+
+ for( ; xi < k; ++xi )
+ {
+ sum += x[0][xi];
+ sum2 += _ts_square( x[0][xi] );
+ }
+
+ fp_t mean = sum / k;
+ fp_t mean2 = sum2 / k;
+ fp_t variance = mean2 - _ts_square( mean );
+ fp_t sd = variance >= 0 ? sqrt( variance ) : 0;
+
+ y[0][yi] = (fabs( mean ) > 0) ? sd / fabs( mean ) * 100 : 0;
+
+ fp_t rk = _ts_reciprocal( k );
+
+ for( ; xi < n; ++xi, ++yi )
+ {
+ sum = sum + ( x[0][xi] - x[0][xi - k] );
+ sum2 = sum2 + ( _ts_square( x[0][xi] ) - _ts_square( x[0][xi - k] ) );
+
+ mean = sum * rk;
+ mean2 = sum2 * rk;
+ variance = mean2 - _ts_square( mean );
+ sd = variance >= 0 ? sqrt( variance ) : 0;
+
+ y[0][yi] = (fabs( mean ) > 0) ? sd / fabs( mean ) * 100 : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/relu.c b/lib/relu.c
new file mode 100644
index 0000000..4e96edb
--- /dev/null
+++ b/lib/relu.c
@@ -0,0 +1,40 @@
+// ReLU
+//
+// relu - Rectified Linear Unit
+//
+// y[t] = max( 0, x[t] )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_relu_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_relu_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_relu(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_relu_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xi = x[0][i];
+ y[0][i] = xi > 0 ? xi : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/remainder.c b/lib/remainder.c
new file mode 100644
index 0000000..c3a6df7
--- /dev/null
+++ b/lib/remainder.c
@@ -0,0 +1,41 @@
+// Remainder
+//
+// remainder
+//
+// IEEE remainder
+//
+//
+//
+// Inputs: 2
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_remainder_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_remainder_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_remainder(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_remainder_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ if( x[1][i] == 0.0 ) { y[0][i] = 0; continue; }
+ y[0][i] = remainder( x[0][i], x[1][i] );
+ }
+ return 0;
+}
+
diff --git a/lib/remquo_dummy.c b/lib/remquo_dummy.c
new file mode 100644
index 0000000..3f9bd48
--- /dev/null
+++ b/lib/remquo_dummy.c
@@ -0,0 +1,40 @@
+// Remquo Dummy
+//
+// remquo_dummy
+//
+// Remainder with quotient
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_remquo_dummy_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_remquo_dummy_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_remquo_dummy(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_remquo_dummy_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = remainder( x[0][i], 1.0 );
+ }
+ return 0;
+}
+
diff --git a/lib/reversal.c b/lib/reversal.c
new file mode 100644
index 0000000..ede74fe
--- /dev/null
+++ b/lib/reversal.c
@@ -0,0 +1,52 @@
+// Reversal
+//
+// reversal
+//
+// Reversal indicator
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_reversal_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_reversal_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 2 );
+}
+
+int ts_reversal(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_reversal_p );
+ (void)c;
+ if (n > 0) y[0][0] = 0;
+ if (n > 1) y[0][1] = 0;
+ for( uint_t i = 2; i < n; ++i )
+ {
+ if( x[0][i] < x[0][i - 1] && x[0][i - 1] > x[0][i - 2] )
+ {
+ y[0][i] = -1;
+ }
+ else if( x[0][i] > x[0][i - 1] && x[0][i - 1] < x[0][i - 2] )
+ {
+ y[0][i] = 1;
+ }
+ else
+ {
+ y[0][i] = 0;
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/rint.c b/lib/rint.c
new file mode 100644
index 0000000..fb744fb
--- /dev/null
+++ b/lib/rint.c
@@ -0,0 +1,40 @@
+// Rint
+//
+// rint
+//
+// Round to integer
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_rint_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_rint_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_rint(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_rint_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = rint( x[0][i] );
+ }
+ return 0;
+}
+
diff --git a/lib/roc.c b/lib/roc.c
new file mode 100644
index 0000000..3b45ecd
--- /dev/null
+++ b/lib/roc.c
@@ -0,0 +1,47 @@
+// Roc
+//
+// roc
+//
+// Rate of Change
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_roc_params_t;
+
+ts_parameter_t const ts_roc_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_roc_params_t) };
+
+uint_t ts_roc_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_roc_params_t *)c)->k : 0 );
+}
+
+int ts_roc(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_roc_p );
+ if ( !c ) return -1;
+ TS_CHECK_INPUT(x, x_n, n);
+ uint_t k = ((ts_roc_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for( uint_t i = k; i < n; ++i )
+ {
+ if( x[0][i - k] == 0 ) { y[0][i] = 0; continue; }
+ y[0][i] = ( x[0][i] - x[0][i - k] ) / x[0][i - k] * 100;
+ }
+ return 0;
+}
+
diff --git a/lib/rocp.c b/lib/rocp.c
new file mode 100644
index 0000000..6575d85
--- /dev/null
+++ b/lib/rocp.c
@@ -0,0 +1,46 @@
+// Rocp
+//
+// rocp
+//
+// Rate of Change Percentage
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_rocp_params_t;
+
+ts_parameter_t const ts_rocp_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_rocp_params_t) };
+
+uint_t ts_rocp_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_rocp_params_t *)c)->k : 0 );
+}
+
+int ts_rocp(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_rocp_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_rocp_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for( uint_t i = k; i < n; ++i )
+ {
+ if( x[0][i - k] == 0 ) { y[0][i] = 0; continue; }
+ y[0][i] = ( x[0][i] - x[0][i - k] ) / x[0][i - k];
+ }
+ return 0;
+}
+
diff --git a/lib/rocr.c b/lib/rocr.c
new file mode 100644
index 0000000..d11cb91
--- /dev/null
+++ b/lib/rocr.c
@@ -0,0 +1,46 @@
+// Rocr
+//
+// rocr
+//
+// Rate of Change Ratio
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_rocr_params_t;
+
+ts_parameter_t const ts_rocr_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_rocr_params_t) };
+
+uint_t ts_rocr_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_rocr_params_t *)c)->k : 0 );
+}
+
+int ts_rocr(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_rocr_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_rocr_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for( uint_t i = k; i < n; ++i )
+ {
+ if( x[0][i - k] == 0 ) { y[0][i] = 0; continue; }
+ y[0][i] = x[0][i] / x[0][i - k];
+ }
+ return 0;
+}
+
diff --git a/lib/rocr100.c b/lib/rocr100.c
new file mode 100644
index 0000000..b90413f
--- /dev/null
+++ b/lib/rocr100.c
@@ -0,0 +1,45 @@
+// Rocr100
+//
+// rocr100
+//
+// Rate of Change Ratio * 100
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_rocr100_params_t;
+
+ts_parameter_t const ts_rocr100_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_rocr100_params_t) };
+
+uint_t ts_rocr100_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_rocr100_params_t *)c)->k : 0 );
+}
+
+int ts_rocr100(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_rocr100_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_rocr100_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for( uint_t i = k; i < n; ++i )
+ {
+ if( x[0][i - k] == 0 ) { y[0][i] = 0; continue; }
+ y[0][i] = x[0][i] / x[0][i - k] * 100;
+ }
+ return 0;
+}
+
diff --git a/lib/root.c b/lib/root.c
new file mode 100755
index 0000000..a5e7dca
--- /dev/null
+++ b/lib/root.c
@@ -0,0 +1,48 @@
+// Nth Root
+//
+// n_root - nrt
+//
+// f( x ) = x^( 1 / n )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 1
+//
+// c[0] = n
+
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ fp_t r;
+} ts_root_params_t;
+
+ts_parameter_t const ts_root_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_root_params_t) };
+
+
+uint_t ts_root_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_root(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_root_p );
+ if ( !c ) return -1;
+ fp_t r = ((ts_root_params_t *)c)->r;
+ if (r == 0) return -1;
+
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = pow( x[0][i], _ts_reciprocal( r ) );
+ }
+ return 0;
+}
+
diff --git a/lib/root_cube.c b/lib/root_cube.c
new file mode 100644
index 0000000..1cd79d5
--- /dev/null
+++ b/lib/root_cube.c
@@ -0,0 +1,41 @@
+// Cube Root
+//
+// cube_root - cbrt
+//
+// f( x ) = ∛x
+// = x^( 1 / 3 )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_root_cube_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_root_cube_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_root_cube(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_root_cube_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = cbrt( x[0][i] );
+ }
+ return 0;
+}
+
diff --git a/lib/root_square.c b/lib/root_square.c
new file mode 100644
index 0000000..3e8ef8e
--- /dev/null
+++ b/lib/root_square.c
@@ -0,0 +1,43 @@
+// Square Root
+//
+// square_root - sqrt
+//
+// f( x ) = √x
+// = x^( 1 / 2 )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+//
+//
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_root_square_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_root_square_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_root_square(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_root_square_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = x[0][i] >= 0 ? sqrt( x[0][i] ) : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/round_.c b/lib/round_.c
new file mode 100644
index 0000000..f061b87
--- /dev/null
+++ b/lib/round_.c
@@ -0,0 +1,40 @@
+// Round
+//
+// round - round
+//
+// f( x ) = ⌊ x + 0.5 ⌋
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_round__p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_round__offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_round_(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_round__p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = round( x[0][i] );
+ }
+ return 0;
+}
+
diff --git a/lib/rsi.c b/lib/rsi.c
new file mode 100644
index 0000000..bf21c5e
--- /dev/null
+++ b/lib/rsi.c
@@ -0,0 +1,91 @@
+// Rsi
+//
+// rsi
+//
+// Relative Strength Index
+//
+// RSI = 100 - (100 / (1 + RS)), where RS = avg_gain / avg_loss
+// Uses Wilder's smoothing for avg_gain and avg_loss.
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_rsi_params_t;
+
+ts_parameter_t const ts_rsi_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_rsi_params_t) };
+
+uint_t ts_rsi_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_rsi_params_t *)c)->k : 0 );
+}
+
+int ts_rsi(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_rsi_p );
+ if ( !c ) return -1;
+ TS_CHECK_INPUT(x, x_n, n);
+ uint_t k = ((ts_rsi_params_t *)c)->k;
+ if (k == 0 || n <= k) return -1;
+
+ for (uint_t i = 0; i < k; ++i) y[0][i] = NAN;
+
+ fp_t avg_gain = 0;
+ fp_t avg_loss = 0;
+
+ for( uint_t i = 1; i <= k; ++i )
+ {
+ fp_t change = x[0][i] - x[0][i - 1];
+ if( change > 0 )
+ avg_gain += change;
+ else if( change < 0 )
+ avg_loss += (-change);
+ }
+
+ avg_gain /= k;
+ avg_loss /= k;
+
+ {
+ if( avg_loss < TS_FILL_ZERO_THRESHOLD )
+ y[0][k] = 100;
+ else
+ {
+ fp_t rs = avg_gain / avg_loss;
+ y[0][k] = 100 - ( 100 / ( 1 + rs ) );
+ }
+ }
+
+ for( uint_t i = k + 1; i < n; ++i )
+ {
+ fp_t change = x[0][i] - x[0][i - 1];
+ fp_t gain = change > 0 ? change : 0;
+ fp_t loss = change < 0 ? (-change) : 0;
+
+ avg_gain = ( avg_gain * ( k - 1 ) + gain ) / k;
+ avg_loss = ( avg_loss * ( k - 1 ) + loss ) / k;
+
+ if( avg_loss < TS_FILL_ZERO_THRESHOLD )
+ y[0][i] = 100;
+ else
+ {
+ fp_t rs = avg_gain / avg_loss;
+ y[0][i] = 100 - ( 100 / ( 1 + rs ) );
+ }
+ }
+
+ return 0;
+}
+
+
diff --git a/lib/running_count.c b/lib/running_count.c
new file mode 100644
index 0000000..fb8d3ad
--- /dev/null
+++ b/lib/running_count.c
@@ -0,0 +1,43 @@
+// Running Count
+//
+// running_count
+//
+// Running count of non-zero values
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_running_count_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_running_count_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_running_count(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_running_count_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ if( i == 0 )
+ y[0][i] = ( x[0][i] != 0 ) ? 1 : 0;
+ else
+ y[0][i] = y[0][i - 1] + ( ( x[0][i] != 0 ) ? 1 : 0 );
+ }
+
+ return 0;
+}
+
diff --git a/lib/schaff_trend_cycle.c b/lib/schaff_trend_cycle.c
new file mode 100644
index 0000000..f4584e6
--- /dev/null
+++ b/lib/schaff_trend_cycle.c
@@ -0,0 +1,151 @@
+// Schaff Trend Cycle
+//
+// schaff_trend_cycle
+//
+// Schaff Trend Cycle
+//
+//
+//
+// Inputs: 1
+// Outputs: 2
+// Constants: 1
+
+
+#include <stdlib.h>
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t period;
+} ts_schaff_trend_cycle_params_t;
+
+ts_parameter_t const ts_schaff_trend_cycle_p = { .n_inputs = 1, .n_outputs = 2, .n_constants = 1, .params_size = sizeof(ts_schaff_trend_cycle_params_t) };
+
+uint_t ts_schaff_trend_cycle_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 52 );
+}
+
+int ts_schaff_trend_cycle(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_schaff_trend_cycle_p );
+ if ( !c ) return -1;
+ uint_t period = ((ts_schaff_trend_cycle_params_t *)c)->period;
+ if (period == 0) period = 3;
+ if (n == 0) return -1;
+ if (period > n) { for (uint_t j = 0; j < n; ++j) { y[0][j] = 0; y[1][j] = 0; } return 0; }
+
+ uint_t fast = 10;
+ uint_t slow = 21;
+ uint_t rsi_period = 13;
+
+ fp_t ema_fast = x[0][0];
+ fp_t ema_slow = x[0][0];
+ fp_t a_fast = 2.0 / (fast + 1);
+ fp_t a_slow = 2.0 / (slow + 1);
+
+ fp_t *macd_line = calloc(n, sizeof(fp_t));
+ if (!macd_line) return -1;
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ ema_fast = ema_fast + a_fast * (x[0][i] - ema_fast);
+ ema_slow = ema_slow + a_slow * (x[0][i] - ema_slow);
+ macd_line[i] = ema_fast - ema_slow;
+ }
+
+ fp_t *rsi1 = calloc(n, sizeof(fp_t));
+ fp_t *rsi2 = calloc(n, sizeof(fp_t));
+ if (!rsi1 || !rsi2) { free(macd_line); free(rsi1); free(rsi2); return -1; }
+
+ {
+ fp_t avg_up = 0, avg_down = 0;
+ for( uint_t i = 1; i <= rsi_period; ++i )
+ {
+ fp_t change = macd_line[i] - macd_line[i - 1];
+ if( change > 0 ) avg_up += change;
+ else avg_down += (-change);
+ }
+ avg_up /= rsi_period;
+ avg_down /= rsi_period;
+ rsi1[rsi_period] = (avg_down == 0) ? 100 : 100 - 100 / (1 + avg_up / avg_down);
+
+ for( uint_t i = rsi_period + 1; i < n; ++i )
+ {
+ fp_t change = macd_line[i] - macd_line[i - 1];
+ fp_t gain = change > 0 ? change : 0;
+ fp_t loss = change < 0 ? (-change) : 0;
+ avg_up = (avg_up * (rsi_period - 1) + gain) / rsi_period;
+ avg_down = (avg_down * (rsi_period - 1) + loss) / rsi_period;
+ rsi1[i] = (avg_down == 0) ? 100 : 100 - 100 / (1 + avg_up / avg_down);
+ }
+ }
+
+ {
+ fp_t avg_up = 0, avg_down = 0;
+ for( uint_t i = rsi_period + 1; i <= rsi_period * 2; ++i )
+ {
+ fp_t change = rsi1[i] - rsi1[i - 1];
+ if( change > 0 ) avg_up += change;
+ else avg_down += (-change);
+ }
+ avg_up /= rsi_period;
+ avg_down /= rsi_period;
+ rsi2[rsi_period * 2] = (avg_down == 0) ? 100 : 100 - 100 / (1 + avg_up / avg_down);
+
+ for( uint_t i = rsi_period * 2 + 1; i < n; ++i )
+ {
+ fp_t change = rsi1[i] - rsi1[i - 1];
+ fp_t gain = change > 0 ? change : 0;
+ fp_t loss = change < 0 ? (-change) : 0;
+ avg_up = (avg_up * (rsi_period - 1) + gain) / rsi_period;
+ avg_down = (avg_down * (rsi_period - 1) + loss) / rsi_period;
+ rsi2[i] = (avg_down == 0) ? 100 : 100 - 100 / (1 + avg_up / avg_down);
+ }
+ }
+
+ uint_t offset = slow + rsi_period * 2;
+ fp_t sig_a = 1.0 / (fp_t)period;
+ fp_t signal_ema = 0;
+ uint_t active = 0;
+ uint_t count = 0;
+
+ for (uint_t j = 0; j < n && j < offset; ++j) { y[0][j] = 0; y[1][j] = 0; }
+ for( uint_t i = offset; i < n; ++i )
+ {
+ fp_t hist = macd_line[i] - macd_line[i - 1];
+ fp_t hist_prev = (i > 1) ? macd_line[i - 1] - macd_line[i - 2] : 0;
+ int prev_above = hist_prev > 0.5;
+ int curr_above = hist > 0.5;
+ fp_t stc = 0;
+
+ if( !prev_above && curr_above )
+ {
+ active = 1;
+ count = 0;
+ }
+ if( active )
+ {
+ stc = 100;
+ count++;
+ if( count >= period )
+ active = 0;
+ }
+
+ y[0][i] = stc;
+
+ signal_ema = signal_ema + sig_a * (stc - signal_ema);
+ y[1][i] = signal_ema;
+ }
+
+ free(macd_line);
+ free(rsi1);
+ free(rsi2);
+ return 0;
+}
+
diff --git a/lib/secant.c b/lib/secant.c
new file mode 100644
index 0000000..128e5a7
--- /dev/null
+++ b/lib/secant.c
@@ -0,0 +1,41 @@
+// Secant
+//
+// secant - sec
+//
+// f( x ) = sec( x ) = 1 / cos( x )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_secant_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_secant_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_secant(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_secant_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t cv = cos( x[0][i] );
+ y[0][i] = ( fabs( cv ) > 1e-15 ) ? _ts_reciprocal( cv ) : ( cv > 0 ? (fp_t)INFINITY : (fp_t)-INFINITY );
+ }
+ return 0;
+}
+
diff --git a/lib/secant_hyperbolic.c b/lib/secant_hyperbolic.c
new file mode 100644
index 0000000..6956840
--- /dev/null
+++ b/lib/secant_hyperbolic.c
@@ -0,0 +1,42 @@
+// Hyperbolic Secant
+//
+// secant_hyperbolic - sech
+//
+// f( x ) = sech( x )
+// = 1 / cosh( x )
+// = 2 / ( e^x + e^( -x ) )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_secant_hyperbolic_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_secant_hyperbolic_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_secant_hyperbolic(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_secant_hyperbolic_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = _ts_reciprocal( cosh( x[0][i] ) );
+ }
+ return 0;
+}
+
diff --git a/lib/secant_hyperbolic_inverse.c b/lib/secant_hyperbolic_inverse.c
new file mode 100644
index 0000000..d530d71
--- /dev/null
+++ b/lib/secant_hyperbolic_inverse.c
@@ -0,0 +1,42 @@
+// Inverse Hyperbolic Secant
+//
+// secant_hyperbolic_inverse - asech
+//
+// f( x ) = asech( x )
+// = ln( ( 1 / x ) + √( ( 1 / x^2 ) - 1 ) ) 0 < x ≤ 1
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_secant_hyperbolic_inverse_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_secant_hyperbolic_inverse_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_secant_hyperbolic_inverse(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_secant_hyperbolic_inverse_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xi = x[0][i];
+ y[0][i] = ( xi > 0 && xi <= 1.0 ) ? acosh( _ts_reciprocal( xi ) ) : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/secant_inverse.c b/lib/secant_inverse.c
new file mode 100644
index 0000000..fbbab80
--- /dev/null
+++ b/lib/secant_inverse.c
@@ -0,0 +1,42 @@
+// Inverse Secant
+//
+// secant_inverse - asec
+//
+// f( x ) = asec( x )
+// = acos( 1 / x )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_secant_inverse_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_secant_inverse_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_secant_inverse(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_secant_inverse_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t r = _ts_reciprocal( x[0][i] );
+ y[0][i] = ( r >= -1.0 && r <= 1.0 ) ? acos( r ) : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/session_volume.c b/lib/session_volume.c
new file mode 100644
index 0000000..be9810f
--- /dev/null
+++ b/lib/session_volume.c
@@ -0,0 +1,43 @@
+// Session Volume
+//
+// session_volume
+//
+// Session volume
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_session_volume_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_session_volume_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_session_volume(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_session_volume_p );
+ (void)c;
+ fp_t cum = 0;
+
+ for( uint_t i = 0; i < n; ++i )
+ {
+ cum += x[0][i];
+ y[0][i] = cum;
+ }
+
+ return 0;
+}
+
diff --git a/lib/shinohara_intensity_ratio.c b/lib/shinohara_intensity_ratio.c
new file mode 100644
index 0000000..fcdd364
--- /dev/null
+++ b/lib/shinohara_intensity_ratio.c
@@ -0,0 +1,85 @@
+// Shinohara Intensity Ratio
+//
+// shinohara_intensity_ratio
+//
+// Shinohara Intensity Ratio
+//
+//
+//
+// Inputs: 4
+// Outputs: 1
+// Constants: 1
+
+
+#include <stdlib.h>
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_shinohara_intensity_ratio_params_t;
+
+ts_parameter_t const ts_shinohara_intensity_ratio_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_shinohara_intensity_ratio_params_t) };
+
+uint_t ts_shinohara_intensity_ratio_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_shinohara_intensity_ratio_params_t *)c)->k > 0 ? ((ts_shinohara_intensity_ratio_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_shinohara_intensity_ratio(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_shinohara_intensity_ratio_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_shinohara_intensity_ratio_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ fp_t *intensity = calloc( n, sizeof( fp_t ) );
+ if ( !intensity ) return -1;
+
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t h = ts_h(x, i);
+ fp_t l = ts_l(x, i);
+ fp_t cl = ts_c(x, i);
+ fp_t o = ts_o(x, i);
+ fp_t range = h - l;
+
+ if( range == 0 )
+ {
+ intensity[i] = 0;
+ }
+ else if( cl > o )
+ {
+ intensity[i] = (h - cl) / range;
+ }
+ else
+ {
+ intensity[i] = (cl - l) / range;
+ }
+ }
+
+ for (uint_t j = 0; j < n && j < k - 1; ++j) y[0][j] = 0;
+ for( uint_t i = k - 1; i < n; ++i )
+ {
+ if( i == k - 1 )
+ {
+ fp_t sum = 0;
+ for( uint_t j = 0; j < k; ++j )
+ sum += intensity[j];
+ y[0][i] = sum / k;
+ }
+ else
+ {
+ y[0][i] = y[0][i-1] + (intensity[i] - intensity[i - k]) / k;
+ }
+ }
+
+ free( intensity );
+ return 0;
+}
+
diff --git a/lib/sigmoid.c b/lib/sigmoid.c
new file mode 100644
index 0000000..05ea126
--- /dev/null
+++ b/lib/sigmoid.c
@@ -0,0 +1,63 @@
+// Sigmoid
+//
+// sigmoid - sig
+//
+// f(x) = ( L / ( 1 + e^( -( 4m * ( x - x_0 ) / L ) ) ) ) + b - ( L / 2 )
+//
+// m = f'(0)
+// b = f(0)
+// ( b - ( L / 2 ) ) < y < ( b + ( L / 2 ) )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 3
+//
+// c[0] = m
+// c[1] = L
+// c[2] = b
+
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ fp_t m;
+ fp_t L;
+ fp_t b;
+} ts_sigmoid_params_t;
+
+ts_parameter_t const ts_sigmoid_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 3, .params_size = sizeof(ts_sigmoid_params_t) };
+
+
+uint_t ts_sigmoid_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_sigmoid(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_sigmoid_p );
+ if ( !c ) return -1;
+ fp_t m = ((ts_sigmoid_params_t *)c)->m;
+ fp_t L = ((ts_sigmoid_params_t *)c)->L;
+ fp_t b = ((ts_sigmoid_params_t *)c)->b;
+
+ m = -( 4.0 * m ) / L;
+ b = b - ( 0.5 * L );
+
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xv = m * x[0][i];
+ if( xv > 500.0 ) { y[0][i] = b; continue; }
+ if( xv < -500.0 ) { y[0][i] = L + b; continue; }
+ y[0][i] = ( L / ( 1.0 + exp( xv ) ) ) + b;
+ }
+ return 0;
+}
+
diff --git a/lib/sign.c b/lib/sign.c
new file mode 100644
index 0000000..d6b1aaf
--- /dev/null
+++ b/lib/sign.c
@@ -0,0 +1,41 @@
+// Sign
+//
+// sign - sgn
+//
+// ⎧ -1 if x < 0
+// f( x ) = ⎨ 0 if x = 0
+// ⎩ 1 if x > 0
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_sign_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_sign_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_sign(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_sign_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = ( x[0][i] > 0 ) - ( x[0][i] < 0 );
+ }
+ return 0;
+}
+
diff --git a/lib/sinc.c b/lib/sinc.c
new file mode 100644
index 0000000..779c8da
--- /dev/null
+++ b/lib/sinc.c
@@ -0,0 +1,47 @@
+// Sinc
+//
+// sinc - Normalized Sinc function
+//
+// y[t] = x[t] == 0 ? 1 : sin( pi * x[t] ) / ( pi * x[t] )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_sinc_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_sinc_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_sinc(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_sinc_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xi = x[0][i];
+ if ( xi == 0 )
+ y[0][i] = 1;
+ else
+ {
+ fp_t pix = PI * xi;
+ y[0][i] = sin( pix ) / pix;
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/sinc_unnormalized.c b/lib/sinc_unnormalized.c
new file mode 100644
index 0000000..c68f9fa
--- /dev/null
+++ b/lib/sinc_unnormalized.c
@@ -0,0 +1,44 @@
+// Sinc Unnormalized
+//
+// sinc_unnormalized - Unnormalized Sinc function
+//
+// y[t] = x[t] == 0 ? 1 : sin( x[t] ) / x[t]
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_sinc_unnormalized_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_sinc_unnormalized_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_sinc_unnormalized(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_sinc_unnormalized_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xi = x[0][i];
+ if ( xi == 0 )
+ y[0][i] = 1;
+ else
+ y[0][i] = sin( xi ) / xi;
+ }
+ return 0;
+}
+
diff --git a/lib/sine.c b/lib/sine.c
new file mode 100644
index 0000000..262d4f8
--- /dev/null
+++ b/lib/sine.c
@@ -0,0 +1,40 @@
+// Sine
+//
+// sine - sin
+//
+// f( x ) = sin( x )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_sine_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_sine_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_sine(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_sine_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = sin( x[0][i] );
+ }
+ return 0;
+}
+
diff --git a/lib/sine_hyperbolic.c b/lib/sine_hyperbolic.c
new file mode 100644
index 0000000..5662f19
--- /dev/null
+++ b/lib/sine_hyperbolic.c
@@ -0,0 +1,41 @@
+// Hyperbolic Sine
+//
+// sine_hyperbolic - sinh
+//
+// f( x ) = sinh( x )
+// = 0.5 * ( e^x - e^( -x ) )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_sine_hyperbolic_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_sine_hyperbolic_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_sine_hyperbolic(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_sine_hyperbolic_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = sinh( x[0][i] );
+ }
+ return 0;
+}
+
diff --git a/lib/sine_hyperbolic_inverse.c b/lib/sine_hyperbolic_inverse.c
new file mode 100644
index 0000000..57c1be5
--- /dev/null
+++ b/lib/sine_hyperbolic_inverse.c
@@ -0,0 +1,41 @@
+// Inverse Hyperbolic Sine
+//
+// sine_hyperbolic_inverse - asinh
+//
+// f( x ) = asinh( x )
+// = ln( x + √( x^2 + 1 ) )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_sine_hyperbolic_inverse_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_sine_hyperbolic_inverse_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_sine_hyperbolic_inverse(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_sine_hyperbolic_inverse_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = asinh( x[0][i] );
+ }
+ return 0;
+}
+
diff --git a/lib/sine_inverse.c b/lib/sine_inverse.c
new file mode 100644
index 0000000..5d96513
--- /dev/null
+++ b/lib/sine_inverse.c
@@ -0,0 +1,41 @@
+// Inverse Sine
+//
+// sine_inverse - asin
+//
+// f( x ) = asin( x )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_sine_inverse_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_sine_inverse_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_sine_inverse(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_sine_inverse_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t v = x[0][i];
+ y[0][i] = ( v >= -1.0 && v <= 1.0 ) ? asin( v ) : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/slope.c b/lib/slope.c
new file mode 100755
index 0000000..ca4fd22
--- /dev/null
+++ b/lib/slope.c
@@ -0,0 +1,51 @@
+// Slope
+//
+// slope - slope
+//
+// Calculates the slope between the current value and the value k samples ago
+//
+// y_i = ( x_i - x_i-k ) / k
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+} ts_slope_params_t;
+
+ts_parameter_t const ts_slope_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_slope_params_t) };
+
+
+uint_t ts_slope_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_slope_params_t *)c)->k : 0 );
+}
+
+int ts_slope(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_slope_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_slope_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ uint_t xi = k;
+
+ fp_t rk = _ts_reciprocal( k );
+
+ for( ; xi < n; xi++ )
+ {
+ y[0][xi] = ( x[0][xi] - x[0][xi - k] ) * rk;
+ }
+ return 0;
+}
+
diff --git a/lib/smootherstep.c b/lib/smootherstep.c
new file mode 100644
index 0000000..3053bcf
--- /dev/null
+++ b/lib/smootherstep.c
@@ -0,0 +1,49 @@
+// Smootherstep
+//
+// smootherstep - Smootherstep interpolation
+//
+// y[t] = x[t] <= 0 ? 0 : x[t] >= 1 ? 1 : 6*x[t]^5 - 15*x[t]^4 + 10*x[t]^3
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_smootherstep_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_smootherstep_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_smootherstep(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_smootherstep_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xi = x[0][i];
+ if ( xi <= 0 )
+ y[0][i] = 0;
+ else if ( xi >= 1 )
+ y[0][i] = 1;
+ else
+ {
+ fp_t x2 = xi * xi;
+ fp_t x3 = x2 * xi;
+ y[0][i] = 6 * x3 * x2 - 15 * x2 * x2 + 10 * x3;
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/smoothstep.c b/lib/smoothstep.c
new file mode 100644
index 0000000..224e68b
--- /dev/null
+++ b/lib/smoothstep.c
@@ -0,0 +1,48 @@
+// Smoothstep
+//
+// smoothstep - Smoothstep interpolation
+//
+// y[t] = x[t] <= 0 ? 0 : x[t] >= 1 ? 1 : 3*x[t]^2 - 2*x[t]^3
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_smoothstep_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_smoothstep_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_smoothstep(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_smoothstep_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xi = x[0][i];
+ if ( xi <= 0 )
+ y[0][i] = 0;
+ else if ( xi >= 1 )
+ y[0][i] = 1;
+ else
+ {
+ fp_t x2 = xi * xi;
+ y[0][i] = 3 * x2 - 2 * x2 * xi;
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/softplus.c b/lib/softplus.c
new file mode 100644
index 0000000..b61f96b
--- /dev/null
+++ b/lib/softplus.c
@@ -0,0 +1,41 @@
+// Softplus
+//
+// softplus - Softplus activation
+//
+// y[t] = log( 1 + exp( x[t] ) )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_softplus_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_softplus_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_softplus(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_softplus_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xi = x[0][i];
+ y[0][i] = xi > 20 ? xi : log( 1 + exp( xi ) );
+ }
+ return 0;
+}
+
diff --git a/lib/softsign.c b/lib/softsign.c
new file mode 100644
index 0000000..978f275
--- /dev/null
+++ b/lib/softsign.c
@@ -0,0 +1,41 @@
+// Softsign
+//
+// softsign - Softsign activation
+//
+// y[t] = x[t] / ( 1 + |x[t]| )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_softsign_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_softsign_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_softsign(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_softsign_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xi = x[0][i];
+ y[0][i] = xi / ( 1 + fabs( xi ) );
+ }
+ return 0;
+}
+
diff --git a/lib/standard_error.c b/lib/standard_error.c
new file mode 100644
index 0000000..9b0ac3b
--- /dev/null
+++ b/lib/standard_error.c
@@ -0,0 +1,67 @@
+// Standard Error
+//
+// standard_error
+//
+// Standard Error of the Mean
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_standard_error_params_t;
+
+ts_parameter_t const ts_standard_error_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_standard_error_params_t) };
+
+uint_t ts_standard_error_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_standard_error_params_t *)c)->k > 0 ? ((ts_standard_error_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_standard_error(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_standard_error_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_standard_error_params_t *)c)->k;
+ if (k < 2) return -1;
+
+ for( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t sum_x = 0, sum_y = 0, sum_xy = 0, sum_x2 = 0, sum_y2 = 0;
+ for( uint_t j = 0; j < k; ++j )
+ {
+ fp_t xv = (fp_t)j;
+ fp_t yv = x[0][i - k + 1 + j];
+ sum_x += xv;
+ sum_y += yv;
+ sum_xy += xv * yv;
+ sum_x2 += xv * xv;
+ sum_y2 += yv * yv;
+ }
+ fp_t nk = (fp_t)k;
+ fp_t denom = nk * sum_x2 - sum_x * sum_x;
+ if( denom == 0 )
+ {
+ y[0][i] = 0;
+ }
+ else
+ {
+ fp_t ss_res = sum_y2 - (sum_y * sum_y) / nk - ((nk * sum_xy - sum_x * sum_y) * (nk * sum_xy - sum_x * sum_y)) / (nk * denom);
+ y[0][i] = ss_res > 0 ? sqrt( ss_res / (nk - 2) ) : 0;
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/starc_bands.c b/lib/starc_bands.c
new file mode 100644
index 0000000..0c545f8
--- /dev/null
+++ b/lib/starc_bands.c
@@ -0,0 +1,70 @@
+// Starc Bands
+//
+// starc_bands
+//
+// STARC Bands
+//
+//
+//
+// Inputs: 4
+// Outputs: 3
+// Constants: 2
+
+
+#include <math.h>
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+ fp_t mult;
+} ts_starc_bands_params_t;
+
+ts_parameter_t const ts_starc_bands_p = { .n_inputs = 4, .n_outputs = 3, .n_constants = 2, .params_size = sizeof(ts_starc_bands_params_t) };
+
+
+uint_t ts_starc_bands_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_starc_bands_params_t *)c)->k : 0 );
+}
+
+int ts_starc_bands(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_starc_bands_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_starc_bands_params_t *)c)->k;
+ if (k == 0) return -1;
+ if (n < k + 1) return -1;
+
+ fp_t mult = ((ts_starc_bands_params_t *)c)->mult;
+
+ for( uint_t i = k; i < n; ++i )
+ {
+ fp_t tr_sum = 0;
+ for( uint_t j = 0; j < k; ++j )
+ {
+ uint_t idx = i - k + 1 + j;
+ fp_t tr = fmax( ts_h( x, idx ) - ts_l( x, idx ), fmax( fabs( ts_h( x, idx ) - ts_c( x, idx - 1 ) ), fabs( ts_l( x, idx ) - ts_c( x, idx - 1 ) ) ) );
+ tr_sum += tr;
+ }
+ fp_t atr = tr_sum / (fp_t)k;
+
+ fp_t sum = 0;
+ for( uint_t j = 0; j < k; ++j )
+ {
+ sum += ts_c( x, i - k + 1 + j );
+ }
+ fp_t mean = sum / k;
+
+ y[0][i] = mean + mult * atr;
+ y[1][i] = mean;
+ y[2][i] = mean - mult * atr;
+ }
+ return 0;
+}
+
diff --git a/lib/starc_lower.c b/lib/starc_lower.c
new file mode 100644
index 0000000..97ac02f
--- /dev/null
+++ b/lib/starc_lower.c
@@ -0,0 +1,74 @@
+// Starc Lower
+//
+// starc_lower
+//
+// STARC Lower Band
+//
+//
+//
+// Inputs: 4
+// Outputs: 1
+// Constants: 2
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+ fp_t mult;
+} ts_starc_lower_params_t;
+
+ts_parameter_t const ts_starc_lower_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 2, .params_size = sizeof(ts_starc_lower_params_t) };
+
+uint_t ts_starc_lower_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_starc_lower_params_t *)c)->k > 0 ? ((ts_starc_lower_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_starc_lower(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_starc_lower_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_starc_lower_params_t *)c)->k;
+ if( k == 0 ) return -1;
+ fp_t mult = ((ts_starc_lower_params_t *)c)->mult;
+
+ uint_t half_k = k / 2;
+ if (n <= half_k) return -1;
+
+ uint_t last = k - 1;
+ for( uint_t i = k - 1; i < n - half_k; ++i )
+ {
+ fp_t middle_sum = 0;
+ fp_t atr_sum = 0;
+
+ for( uint_t j = 0; j < k; ++j )
+ {
+ uint_t idx = i - half_k + j;
+ middle_sum += ts_c( x, idx );
+
+ fp_t tr = ts_h( x, idx ) - ts_l( x, idx );
+ if( idx > 0 )
+ {
+ fp_t h_pc = fabs( ts_h( x, idx ) - ts_c( x, idx - 1 ) );
+ fp_t l_pc = fabs( ts_l( x, idx ) - ts_c( x, idx - 1 ) );
+ if( h_pc > tr ) tr = h_pc;
+ if( l_pc > tr ) tr = l_pc;
+ }
+ atr_sum += tr;
+ }
+
+ y[0][i] = middle_sum / k - mult * atr_sum / k;
+ last = i;
+ }
+ for( uint_t i = last + 1; i < n; ++i )
+ y[0][i] = y[0][last];
+ return 0;
+}
+
diff --git a/lib/starc_upper.c b/lib/starc_upper.c
new file mode 100644
index 0000000..bcccb95
--- /dev/null
+++ b/lib/starc_upper.c
@@ -0,0 +1,64 @@
+// Starc Upper
+//
+// starc_upper
+//
+// STARC Upper Band
+//
+//
+//
+// Inputs: 4
+// Outputs: 1
+// Constants: 2
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+ fp_t mult;
+} ts_starc_upper_params_t;
+
+ts_parameter_t const ts_starc_upper_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 2, .params_size = sizeof(ts_starc_upper_params_t) };
+
+uint_t ts_starc_upper_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_starc_upper_params_t *)c)->k : 0 );
+}
+
+int ts_starc_upper(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_starc_upper_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_starc_upper_params_t *)c)->k;
+ fp_t mult = ((ts_starc_upper_params_t *)c)->mult;
+ if ( k == 0 || k >= n ) return -1;
+
+ for ( uint_t i = k; i < n; ++i )
+ {
+ fp_t tr_sum = 0;
+ for ( uint_t j = 0; j < k; ++j )
+ {
+ uint_t idx = i - k + 1 + j;
+ fp_t tr = fmax( ts_h( x, idx ) - ts_l( x, idx ), fmax( fabs( ts_h( x, idx ) - ts_c( x, idx - 1 ) ), fabs( ts_l( x, idx ) - ts_c( x, idx - 1 ) ) ) );
+ tr_sum += tr;
+ }
+ fp_t atr = tr_sum / (fp_t)k;
+
+ fp_t sum = 0;
+ for ( uint_t j = 0; j < k; ++j )
+ {
+ sum += ts_c( x, i - k + 1 + j );
+ }
+ fp_t mean = sum / k;
+
+ y[0][i] = mean + mult * atr;
+ }
+ return 0;
+}
+
diff --git a/lib/std_error.c b/lib/std_error.c
new file mode 100644
index 0000000..f55d34e
--- /dev/null
+++ b/lib/std_error.c
@@ -0,0 +1,57 @@
+// Std Error
+//
+// std_error
+//
+// Standard Error
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_std_error_params_t;
+
+ts_parameter_t const ts_std_error_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_std_error_params_t) };
+
+uint_t ts_std_error_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_std_error_params_t *)c)->k > 0 ? ((ts_std_error_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_std_error(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_std_error_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_std_error_params_t *)c)->k;
+ if ( k == 0 ) return -1;
+
+ for( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t sum = 0;
+ for( uint_t j = 0; j < k; ++j )
+ sum += x[0][i - k + 1 + j];
+
+ fp_t mean = sum / k;
+
+ fp_t sum_sq = 0;
+ for( uint_t j = 0; j < k; ++j )
+ sum_sq += _ts_square( x[0][i - k + 1 + j] );
+
+ fp_t variance = sum_sq / k - _ts_square( mean );
+ y[0][i] = variance >= 0 ? sqrt( variance ) / sqrt( (fp_t)k ) : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/stoch_rsi.c b/lib/stoch_rsi.c
new file mode 100644
index 0000000..81992a6
--- /dev/null
+++ b/lib/stoch_rsi.c
@@ -0,0 +1,96 @@
+// Stoch Rsi
+//
+// stoch_rsi
+//
+// Stochastic RSI
+//
+//
+//
+// Inputs: 1
+// Outputs: 2
+// Constants: 2
+
+
+#include <math.h>
+#include <stdlib.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t rsi_period;
+ uint_t stoch_period;
+} ts_stoch_rsi_params_t;
+
+ts_parameter_t const ts_stoch_rsi_p = { .n_inputs = 1, .n_outputs = 2, .n_constants = 2, .params_size = sizeof(ts_stoch_rsi_params_t) };
+
+uint_t ts_stoch_rsi_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_stoch_rsi_params_t *)c)->rsi_period + ((ts_stoch_rsi_params_t *)c)->stoch_period - 1 : 0 );
+}
+
+int ts_stoch_rsi(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_stoch_rsi_p );
+ if ( !c ) return -1;
+ uint_t rsi_period = ((ts_stoch_rsi_params_t *)c)->rsi_period;
+ uint_t stoch_period = ((ts_stoch_rsi_params_t *)c)->stoch_period;
+ if (rsi_period == 0 || stoch_period == 0) return -1;
+
+ fp_t *rsi_values = NULL;
+ if( n > 0 )
+ {
+ rsi_values = calloc( n, sizeof( fp_t ) );
+ if( !rsi_values ) return -1;
+ }
+
+ for( uint_t i = rsi_period; i < n; ++i )
+ {
+ fp_t sum_gain = 0;
+ fp_t sum_loss = 0;
+
+ for( uint_t j = 0; j < rsi_period; ++j )
+ {
+ fp_t change = x[0][i - rsi_period + 1 + j] - x[0][i - rsi_period + j];
+ if( change > 0 )
+ sum_gain += change;
+ else if( change < 0 )
+ sum_loss += (-change);
+ }
+
+ if( sum_loss == 0 )
+ rsi_values[i] = 100.0;
+ else
+ {
+ fp_t rs = sum_gain / sum_loss;
+ rsi_values[i] = 100.0 - ( 100.0 / ( 1.0 + rs ) );
+ }
+ }
+
+ for (uint_t j = 0; j < n && j < rsi_period + stoch_period - 1; ++j) { y[0][j] = 0; y[1][j] = 0; }
+ for( uint_t i = rsi_period + stoch_period - 1; i < n; ++i )
+ {
+ fp_t rsi_min = rsi_values[i - stoch_period + 1];
+ fp_t rsi_max = rsi_values[i - stoch_period + 1];
+
+ for( uint_t j = 1; j < stoch_period; ++j )
+ {
+ rsi_min = fmin( rsi_min, rsi_values[i - stoch_period + 1 + j] );
+ rsi_max = fmax( rsi_max, rsi_values[i - stoch_period + 1 + j] );
+ }
+
+ fp_t denom = rsi_max - rsi_min;
+ fp_t stoch_k = denom != 0 ? ( rsi_values[i] - rsi_min ) / denom : 0;
+ fp_t stoch_d = stoch_k;
+
+ y[0][i] = stoch_k;
+ y[1][i] = stoch_d;
+ }
+
+ free( rsi_values );
+ return 0;
+}
+
diff --git a/lib/stochastic_fast.c b/lib/stochastic_fast.c
new file mode 100644
index 0000000..eb203c8
--- /dev/null
+++ b/lib/stochastic_fast.c
@@ -0,0 +1,94 @@
+// Stochastic Fast
+//
+// stochastic_fast
+//
+// Stochastic Fast %K
+//
+//
+//
+// Inputs: 4
+// Outputs: 2
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_stochastic_fast_params_t;
+
+ts_parameter_t const ts_stochastic_fast_p = { .n_inputs = 4, .n_outputs = 2, .n_constants = 1, .params_size = sizeof(ts_stochastic_fast_params_t) };
+
+uint_t ts_stochastic_fast_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_stochastic_fast_params_t *)c)->k > 0 ? 2 * ((ts_stochastic_fast_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_stochastic_fast(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_stochastic_fast_p );
+ if ( !c ) return -1;
+ TS_CHECK_INPUT(x, x_n, n);
+ uint_t k = ((ts_stochastic_fast_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ // Initial window max/min
+ fp_t hh = ts_h( x, 0 );
+ fp_t ll = ts_l( x, 0 );
+ for( uint_t j = 1; j < k; ++j )
+ {
+ if( ts_h( x, j ) > hh )
+ hh = ts_h( x, j );
+ if( ts_l( x, j ) < ll )
+ ll = ts_l( x, j );
+ }
+
+ {
+ fp_t k_val = ( hh - ll ) == 0 ? 0 : 100.0 * ( ts_c( x, k - 1 ) - ll ) / ( hh - ll );
+ y[0][k - 1] = k_val;
+ }
+
+ for( uint_t i = k; i < n; ++i )
+ {
+ fp_t new_h = ts_h( x, i );
+ fp_t new_l = ts_l( x, i );
+ fp_t old_h = ts_h( x, i - k );
+ fp_t old_l = ts_l( x, i - k );
+
+ if ( new_h > hh ) hh = new_h;
+ if ( new_l < ll ) ll = new_l;
+ if ( old_h == hh || old_l == ll )
+ {
+ hh = ts_h( x, i - k + 1 );
+ ll = ts_l( x, i - k + 1 );
+ for( uint_t j = i - k + 2; j <= i; ++j )
+ {
+ if( ts_h( x, j ) > hh ) hh = ts_h( x, j );
+ if( ts_l( x, j ) < ll ) ll = ts_l( x, j );
+ }
+ }
+
+ fp_t k_val = ( hh - ll ) == 0 ? 0 : 100.0 * ( ts_c( x, i ) - ll ) / ( hh - ll );
+ y[0][i] = k_val;
+
+ if (i >= k + 1)
+ {
+ fp_t d_sum = 0;
+ for( uint_t j = 0; j < 3; ++j )
+ d_sum += y[0][i - j];
+ y[1][i] = d_sum / 3.0;
+ }
+ else
+ {
+ y[1][i] = 0;
+ }
+ }
+
+ return 0;
+}
+
diff --git a/lib/stochastic_momentum_index.c b/lib/stochastic_momentum_index.c
new file mode 100644
index 0000000..5491c30
--- /dev/null
+++ b/lib/stochastic_momentum_index.c
@@ -0,0 +1,130 @@
+// Stochastic Momentum Index
+//
+// stochastic_momentum_index
+//
+// Stochastic Momentum Index
+//
+//
+//
+// Inputs: 4
+// Outputs: 2
+// Constants: 3
+
+
+#include <stdlib.h>
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k1;
+ uint_t k2;
+ uint_t signal_period;
+} ts_stochastic_momentum_index_params_t;
+
+ts_parameter_t const ts_stochastic_momentum_index_p = { .n_inputs = 4, .n_outputs = 2, .n_constants = 3, .params_size = sizeof(ts_stochastic_momentum_index_params_t) };
+
+uint_t ts_stochastic_momentum_index_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_stochastic_momentum_index_params_t *)c)->k1 + ((ts_stochastic_momentum_index_params_t *)c)->k2 : 0 );
+}
+
+int ts_stochastic_momentum_index(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_stochastic_momentum_index_p );
+ if ( !c ) return -1;
+ uint_t k1 = ((ts_stochastic_momentum_index_params_t *)c)->k1;
+ uint_t k2 = ((ts_stochastic_momentum_index_params_t *)c)->k2;
+ uint_t signal_period = ((ts_stochastic_momentum_index_params_t *)c)->signal_period;
+ if (k1 == 0 || k2 == 0) return -1;
+
+ fp_t *midpoint = calloc(n, sizeof(fp_t));
+ fp_t *deviation = calloc(n, sizeof(fp_t));
+ if (!midpoint || !deviation) { free(midpoint); free(deviation); return -1; }
+
+ for( uint_t i = 0; i < n; ++i )
+ {
+ midpoint[i] = (ts_h(x, i) + ts_l(x, i)) / 2.0;
+ deviation[i] = ts_c(x, i) - midpoint[i];
+ }
+
+ fp_t *sma_dev = calloc(n, sizeof(fp_t));
+ fp_t *sma_high = calloc(n, sizeof(fp_t));
+ if (!sma_dev || !sma_high) { free(midpoint); free(deviation); free(sma_dev); free(sma_high); return -1; }
+
+ uint_t start = k1 - 1;
+
+ for( uint_t i = start; i < n; ++i )
+ {
+ fp_t sum_dev = 0;
+ fp_t sum_high = 0;
+
+ for( uint_t j = 0; j < k1; ++j )
+ {
+ sum_dev += deviation[i - j];
+ fp_t abs_dev = fabs(deviation[i - j]);
+ if( abs_dev > sum_high ) sum_high = abs_dev;
+ }
+
+ sma_dev[i] = sum_dev / k1;
+ sma_high[i] = sum_high;
+ }
+
+ uint_t start2 = start + k2 - 1;
+
+ fp_t *smi = calloc(n, sizeof(fp_t));
+ if (!smi) { free(midpoint); free(deviation); free(sma_dev); free(sma_high); free(smi); return -1; }
+
+ for( uint_t i = start2; i < n; ++i )
+ {
+ fp_t sum_sma_dev = 0;
+ fp_t sum_sma_high = 0;
+
+ for( uint_t j = 0; j < k2; ++j )
+ {
+ sum_sma_dev += sma_dev[i - j];
+ sum_sma_high += sma_high[i - j];
+ }
+
+ sum_sma_dev /= k2;
+ sum_sma_high /= k2;
+
+ if( sum_sma_high == 0 )
+ smi[i] = 0;
+ else
+ smi[i] = (sum_sma_dev / sum_sma_high) * 100;
+ }
+
+ fp_t sig_a = 2.0 / (signal_period + 1);
+ fp_t signal_ema = 0;
+ uint_t signal_init = 0;
+
+ for (uint_t j = 0; j < n && j < start2; ++j) { y[0][j] = 0; y[1][j] = 0; }
+ for( uint_t i = start2; i < n; ++i )
+ {
+ y[0][i] = smi[i];
+
+ if( !signal_init )
+ {
+ signal_ema = smi[i];
+ signal_init = 1;
+ }
+ else
+ {
+ signal_ema = signal_ema + sig_a * (smi[i] - signal_ema);
+ }
+ y[1][i] = signal_ema;
+ }
+
+ free(midpoint);
+ free(deviation);
+ free(sma_dev);
+ free(sma_high);
+ free(smi);
+ return 0;
+}
+
diff --git a/lib/stochastic_slow.c b/lib/stochastic_slow.c
new file mode 100644
index 0000000..0f84e1a
--- /dev/null
+++ b/lib/stochastic_slow.c
@@ -0,0 +1,91 @@
+// Stochastic Slow
+//
+// stochastic_slow
+//
+// Stochastic Slow %K, %D
+//
+//
+//
+// Inputs: 4
+// Outputs: 2
+// Constants: 1
+
+
+#include <stdlib.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_stochastic_slow_params_t;
+
+ts_parameter_t const ts_stochastic_slow_p = { .n_inputs = 4, .n_outputs = 2, .n_constants = 1, .params_size = sizeof(ts_stochastic_slow_params_t) };
+
+uint_t ts_stochastic_slow_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_stochastic_slow_params_t *)c)->k > 0 ? 3 * ((ts_stochastic_slow_params_t *)c)->k - 3 : 0 : 0 );
+}
+
+int ts_stochastic_slow(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_stochastic_slow_p );
+ if ( !c ) return -1;
+ TS_CHECK_INPUT(x, x_n, n);
+ uint_t k = ((ts_stochastic_slow_params_t *)c)->k;
+ if (k == 0) return -1;
+
+ fp_t *fast_k = calloc( k, sizeof( fp_t ) );
+ if( !fast_k ) return -1;
+
+ for( uint_t i = 0; i < n; ++i )
+ {
+ if( i < k - 1 )
+ {
+ fast_k[i % k] = 0;
+ continue;
+ }
+
+ fp_t hh = ts_h( x, i );
+ fp_t ll = ts_l( x, i );
+
+ for( uint_t j = 1; j < k; ++j )
+ {
+ if( ts_h( x, i - j ) > hh )
+ hh = ts_h( x, i - j );
+ if( ts_l( x, i - j ) < ll )
+ ll = ts_l( x, i - j );
+ }
+
+ fast_k[i % k] = ( hh - ll ) == 0 ? 0 : 100.0 * ( ts_c( x, i ) - ll ) / ( hh - ll );
+ }
+
+ for (uint_t j = 0; j < n && j < 3 * k - 3; ++j) { y[0][j] = 0; y[1][j] = 0; }
+ for( uint_t i = 3 * k - 3; i < n; ++i )
+ {
+ fp_t slow_k_sum = 0;
+ for( uint_t j = 0; j < k; ++j )
+ slow_k_sum += fast_k[(i - j) % k];
+ fp_t slow_k = slow_k_sum / k;
+
+ fp_t slow_d_sum = 0;
+ for( uint_t j = 0; j < k; ++j )
+ {
+ fp_t sk_sum = 0;
+ for( uint_t jj = 0; jj < k; ++jj )
+ sk_sum += fast_k[(i - j - jj) % k];
+ slow_d_sum += sk_sum / k;
+ }
+ fp_t slow_d = slow_d_sum / k;
+
+ y[0][i] = slow_k;
+ y[1][i] = slow_d;
+ }
+
+ free( fast_k );
+ return 0;
+}
+
diff --git a/lib/student_t_pdf.c b/lib/student_t_pdf.c
new file mode 100644
index 0000000..67c9f1d
--- /dev/null
+++ b/lib/student_t_pdf.c
@@ -0,0 +1,51 @@
+// Student T Pdf
+//
+// student_t_pdf
+//
+// Student's t probability density
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ fp_t nu;
+} ts_student_t_pdf_params_t;
+
+ts_parameter_t const ts_student_t_pdf_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_student_t_pdf_params_t) };
+
+uint_t ts_student_t_pdf_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_student_t_pdf(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_student_t_pdf_p );
+ if ( !c ) return -1;
+ fp_t nu = ((ts_student_t_pdf_params_t *)c)->nu;
+ if ( nu <= 0 )
+ {
+ for( uint_t i = 0; i < n; ++i ) y[0][i] = 0;
+ return -1;
+ }
+ fp_t coeff = tgamma( ( nu + 1.0 ) / 2.0 ) / ( sqrt( nu * PI ) * tgamma( nu / 2.0 ) );
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xi = x[0][i];
+ y[0][i] = coeff * pow( 1.0 + xi * xi / nu, -( nu + 1.0 ) / 2.0 );
+ }
+ return 0;
+}
+
diff --git a/lib/subtraction.c b/lib/subtraction.c
new file mode 100644
index 0000000..01fd208
--- /dev/null
+++ b/lib/subtraction.c
@@ -0,0 +1,39 @@
+// Subtraction
+//
+// subtraction - sub
+//
+// f( x_0, x_1 ) = x_0 - x_1
+//
+// X_n - 2
+// Y_n - 1
+// C_n - 0
+
+
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_subtraction_p = { .n_inputs = 2, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_subtraction_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_subtraction(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_subtraction_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = x[0][i] - x[1][i];
+ }
+ return 0;
+}
+
diff --git a/lib/summation.c b/lib/summation.c
new file mode 100755
index 0000000..6d0020b
--- /dev/null
+++ b/lib/summation.c
@@ -0,0 +1,57 @@
+// Summation
+//
+// summation - sum
+//
+// Calculates the sum of the past k values
+//
+// y_i = sum_{j=i-k+1}^{i}[x_j]
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+} ts_summation_params_t;
+
+ts_parameter_t const ts_summation_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_summation_params_t) };
+
+
+uint_t ts_summation_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_summation_params_t *)c)->k > 0 ? ((ts_summation_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_summation(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_summation_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_summation_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ uint_t xi = 0;
+ uint_t yi = k;
+
+ y[0][k - 1] = 0;
+
+ for( ; xi < k; xi++ )
+ {
+ y[0][k - 1] += x[0][xi];
+ }
+
+ for( ; xi < n; xi++, yi++ )
+ {
+ y[0][yi] = x[0][xi] + y[0][yi - 1] - x[0][xi - k];
+ }
+ return 0;
+}
+
diff --git a/lib/supertrend.c b/lib/supertrend.c
new file mode 100644
index 0000000..4487e96
--- /dev/null
+++ b/lib/supertrend.c
@@ -0,0 +1,118 @@
+// Supertrend
+//
+// supertrend
+//
+// SuperTrend Indicator
+//
+//
+//
+// Inputs: 4
+// Outputs: 2
+// Constants: 2
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+ fp_t mult;
+} ts_supertrend_params_t;
+
+ts_parameter_t const ts_supertrend_p = { .n_inputs = 4, .n_outputs = 2, .n_constants = 2, .params_size = sizeof(ts_supertrend_params_t) };
+
+uint_t ts_supertrend_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_supertrend_params_t *)c)->k : 0 );
+}
+
+int ts_supertrend(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_supertrend_p );
+ if ( !c ) return -1;
+ TS_CHECK_INPUT(x, x_n, n);
+ uint_t k = ((ts_supertrend_params_t *)c)->k;
+ fp_t mult = ((ts_supertrend_params_t *)c)->mult;
+ if (k == 0 || n <= k) return -1;
+
+ fp_t tr = ts_h( x, 1 ) - ts_l( x, 1 );
+ fp_t tr2 = fabs( ts_h( x, 1 ) - ts_c( x, 0 ) );
+ fp_t tr3 = fabs( ts_l( x, 1 ) - ts_c( x, 0 ) );
+ if( tr2 > tr ) tr = tr2;
+ if( tr3 > tr ) tr = tr3;
+ fp_t atr = tr;
+
+ for( uint_t i = 2; i <= k; ++i )
+ {
+ tr = ts_h( x, i ) - ts_l( x, i );
+ tr2 = fabs( ts_h( x, i ) - ts_c( x, i - 1 ) );
+ tr3 = fabs( ts_l( x, i ) - ts_c( x, i - 1 ) );
+ if( tr2 > tr ) tr = tr2;
+ if( tr3 > tr ) tr = tr3;
+ atr = atr + (tr - atr) / k;
+ }
+
+ fp_t median = ( ts_h( x, k ) + ts_l( x, k ) ) / 2.0;
+ fp_t raw_upper = median + mult * atr;
+ fp_t raw_lower = median - mult * atr;
+
+ fp_t final_upper = raw_upper;
+ fp_t final_lower = raw_lower;
+ int_t trend = 1;
+
+ if( ts_c( x, k ) > raw_lower )
+ {
+ trend = 1;
+ }
+ else
+ {
+ trend = -1;
+ }
+
+ y[0][k] = trend == 1 ? final_lower : final_upper;
+ y[1][k] = trend;
+
+ for( uint_t i = k + 1; i < n; ++i )
+ {
+ tr = ts_h( x, i ) - ts_l( x, i );
+ tr2 = fabs( ts_h( x, i ) - ts_c( x, i - 1 ) );
+ tr3 = fabs( ts_l( x, i ) - ts_c( x, i - 1 ) );
+ if( tr2 > tr ) tr = tr2;
+ if( tr3 > tr ) tr = tr3;
+ atr = atr + (tr - atr) / k;
+
+ median = ( ts_h( x, i ) + ts_l( x, i ) ) / 2.0;
+ raw_upper = median + mult * atr;
+ raw_lower = median - mult * atr;
+
+ if( raw_upper < final_upper || ts_c( x, i - 1 ) > final_upper )
+ final_upper = raw_upper;
+
+ if( raw_lower > final_lower || ts_c( x, i - 1 ) < final_lower )
+ final_lower = raw_lower;
+
+ int_t new_trend = trend;
+
+ if( trend == 1 )
+ {
+ if( ts_c( x, i ) < final_lower )
+ new_trend = -1;
+ }
+ else
+ {
+ if( ts_c( x, i ) > final_upper )
+ new_trend = 1;
+ }
+
+ trend = new_trend;
+ y[0][i] = trend == 1 ? final_lower : final_upper;
+ y[1][i] = trend;
+ }
+ return 0;
+}
+
diff --git a/lib/swing_index.c b/lib/swing_index.c
new file mode 100644
index 0000000..2f7c563
--- /dev/null
+++ b/lib/swing_index.c
@@ -0,0 +1,52 @@
+// Swing Index
+//
+// swing_index
+//
+// Swing Index
+//
+//
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_swing_index_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_swing_index_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 1 );
+}
+
+int ts_swing_index(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_swing_index_p );
+ (void)c;
+ if (n > 0) y[0][0] = 0;
+ for( uint_t i = 1; i < n; ++i )
+ {
+ fp_t a1 = ts_h( x, i ) - ts_h( x, i - 1 );
+ fp_t a2 = ts_l( x, i - 1 ) - ts_l( x, i );
+ fp_t a = fmax( a1, a2 );
+ fp_t b = ts_c( x, i ) - ts_c( x, i - 1 );
+ fp_t ldpl = ts_l( x, i ) - ts_l( x, i - 1 );
+ fp_t phmh = ts_h( x, i - 1 ) - ts_h( x, i );
+ fp_t c_val = 0;
+ if( b < 0 && ldpl > phmh ) c_val = ldpl;
+ else if( b >= 0 && phmh > ldpl ) c_val = phmh;
+ fp_t hl = ts_h( x, i ) - ts_l( x, i );
+ fp_t swing = ( hl != 0 ) ? ( a * 2.0 + b - c_val ) * ts_v( x, i ) / hl : 0;
+ y[0][i] = ( i > 1 ? y[0][i - 1] : 0 ) + swing;
+ }
+ return 0;
+}
+
diff --git a/lib/swish.c b/lib/swish.c
new file mode 100644
index 0000000..7d09787
--- /dev/null
+++ b/lib/swish.c
@@ -0,0 +1,41 @@
+// Swish
+//
+// swish - Swish (Silu) activation
+//
+// y[t] = x[t] / ( 1 + exp( -x[t] ) )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_swish_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_swish_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_swish(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_swish_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xi = x[0][i];
+ y[0][i] = xi / ( 1 + exp( -xi ) );
+ }
+ return 0;
+}
+
diff --git a/lib/synthetic_yield.c b/lib/synthetic_yield.c
new file mode 100644
index 0000000..c2c8d62
--- /dev/null
+++ b/lib/synthetic_yield.c
@@ -0,0 +1,47 @@
+// Synthetic Yield
+//
+// synthetic_yield
+//
+// Synthetic Yield
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_synthetic_yield_params_t;
+
+ts_parameter_t const ts_synthetic_yield_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_synthetic_yield_params_t) };
+
+uint_t ts_synthetic_yield_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_synthetic_yield_params_t *)c)->k : 0 );
+}
+
+int ts_synthetic_yield(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_synthetic_yield_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_synthetic_yield_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for( uint_t i = k; i < n; ++i )
+ {
+ if( x[0][i - k] == 0 ) { y[0][i] = 0; continue; }
+ y[0][i] = ( x[0][i] - x[0][i - k] ) / x[0][i - k] * 252.0 / k * 100.0;
+ }
+ return 0;
+}
+
diff --git a/lib/t3.c b/lib/t3.c
new file mode 100644
index 0000000..4689d54
--- /dev/null
+++ b/lib/t3.c
@@ -0,0 +1,66 @@
+// T3
+//
+// t3
+//
+// T3 Moving Average
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+ts_parameter_t const ts_t3_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_t3_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_t3(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_t3_p );
+ (void)c;
+ if (n == 0) return -1;
+
+ uint_t k = 5;
+
+ fp_t b = 0.7;
+
+ fp_t alpha = 2.0 / ( (fp_t)k + 1.0 );
+
+ fp_t c3 = b;
+ fp_t c3m1 = c3 - 1.0;
+ fp_t c3m2 = c3 - 2.0;
+ fp_t c3m3 = c3 - 3.0;
+
+ fp_t w3 = c3;
+ fp_t w2 = -3.0 * c3m1;
+ fp_t w1 = 3.0 * c3m1 * c3m2;
+ fp_t w0 = -c3m1 * c3m2 * c3m3;
+
+ fp_t ema1 = x[0][0];
+ fp_t ema2 = x[0][0];
+ fp_t ema3 = x[0][0];
+
+ y[0][0] = x[0][0];
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ ema1 = ema1 + alpha * ( x[0][i] - ema1 );
+ ema2 = ema2 + alpha * ( ema1 - ema2 );
+ ema3 = ema3 + alpha * ( ema2 - ema3 );
+
+ y[0][i] = w3 * ema3 + w2 * ema2 + w1 * ema1 + w0 * x[0][i];
+ }
+ return 0;
+}
+
diff --git a/lib/tangent.c b/lib/tangent.c
new file mode 100644
index 0000000..407ea6b
--- /dev/null
+++ b/lib/tangent.c
@@ -0,0 +1,40 @@
+// Tangent
+//
+// tangent - tan
+//
+// f( x ) = tan( x )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_tangent_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_tangent_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_tangent(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_tangent_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = tan( x[0][i] );
+ }
+ return 0;
+}
+
diff --git a/lib/tangent_hyperbolic.c b/lib/tangent_hyperbolic.c
new file mode 100644
index 0000000..7a76c82
--- /dev/null
+++ b/lib/tangent_hyperbolic.c
@@ -0,0 +1,41 @@
+// Hyperbolic Tangent
+//
+// tangent_hyperbolic - tanh
+//
+// f( x ) = tanh( x )
+// = sinh( x ) / cosh( x )
+// = ( e^2x - 1 ) / ( e^2x + 1 )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_tangent_hyperbolic_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_tangent_hyperbolic_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_tangent_hyperbolic(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_tangent_hyperbolic_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = tanh( x[0][i] );
+ }
+ return 0;
+}
+
diff --git a/lib/tangent_hyperbolic_inverse.c b/lib/tangent_hyperbolic_inverse.c
new file mode 100644
index 0000000..1c337ba
--- /dev/null
+++ b/lib/tangent_hyperbolic_inverse.c
@@ -0,0 +1,43 @@
+// Inverse Hyperbolic Tangent
+//
+// tangent_hyperbolic_inverse - atanh
+//
+// f( x ) = atanh( x )
+// = 0.5 * ln( ( 1 + x ) / ( 1 - x ) ) |x| < 1
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_tangent_hyperbolic_inverse_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_tangent_hyperbolic_inverse_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_tangent_hyperbolic_inverse(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_tangent_hyperbolic_inverse_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xv = x[0][i];
+ if( fabs(xv) >= 1.0 ) { y[0][i] = 0; continue; }
+ y[0][i] = atanh( xv );
+ }
+ return 0;
+}
+
diff --git a/lib/tangent_inverse.c b/lib/tangent_inverse.c
new file mode 100644
index 0000000..69b67b7
--- /dev/null
+++ b/lib/tangent_inverse.c
@@ -0,0 +1,40 @@
+// Inverse Tangent
+//
+// tangent_inverse - atan
+//
+// f( x ) = atan( x )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_tangent_inverse_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_tangent_inverse_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_tangent_inverse(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_tangent_inverse_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = atan( x[0][i] );
+ }
+ return 0;
+}
+
diff --git a/lib/tanhshrink.c b/lib/tanhshrink.c
new file mode 100644
index 0000000..c48ab85
--- /dev/null
+++ b/lib/tanhshrink.c
@@ -0,0 +1,41 @@
+// Tanhshrink
+//
+// tanhshrink - Tanhshrink activation
+//
+// y[t] = x[t] - tanh( x[t] )
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_tanhshrink_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_tanhshrink_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_tanhshrink(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_tanhshrink_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xi = x[0][i];
+ y[0][i] = xi - tanh( xi );
+ }
+ return 0;
+}
+
diff --git a/lib/test.c b/lib/test.c
new file mode 100755
index 0000000..56c47eb
--- /dev/null
+++ b/lib/test.c
@@ -0,0 +1,77 @@
+#include <assert.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "ts.h"
+
+
+void _ts_assert_approximate( fp_t y0, fp_t y1, fp_t e )
+{
+ assert( ( fabs( y0 - y1 ) <= e ) );
+}
+
+
+bool _ts_is_approximate( fp_t y0, fp_t y1, fp_t e )
+{
+ return ( fabs( y0 - y1 ) <= e );
+}
+
+
+#define _ts_test_print( FUNCTION ) \
+ \
+ fputs( #FUNCTION, stdout ); \
+ fputs( ": ", stdout )
+
+
+void ts_test_all( void )
+{
+ ts_test_absolute_value();
+ ts_test_addition();
+ ts_test_ceiling();
+
+}
+
+bool ts_test(
+ void ( *ts_function )(
+ fp_t * const *y,
+ fp_t const * const *x,
+ uint_t n,
+ ts_constant_t const *c
+ ),
+ uint_t ( *ts_offset )(
+ ts_constant_t const *c
+ ),
+ fp_t const * const *x,
+ fp_t const * const *e,
+ uint_t n,
+ ts_constant_t const *c,
+ ts_parameter_t const *m )
+{
+ uint_t offset = ts_offset( c );
+ uint_t y_n = n - offset;
+ bool result = true;
+
+ fp_t *y[m->y];
+
+ for( uint_t i = 0; i < m->y; i++ )
+ {
+ fp_t *tmp = malloc( y_n * sizeof( fp_t ) );
+ y[i] = tmp;
+ }
+
+ ts_function( y, x, n, c );
+
+ for( uint_t i = 0; i < m->y; ++i )
+ {
+ for( uint_t j = 0; j < y_n; ++j )
+ {
+ // _ts_assert_approximate( y[i][j], e[i][j], TS_TEST_EPSILON );
+ result &= _ts_is_approximate( y[i][j], e[i][j], TS_TEST_EPSILON );
+ }
+
+ free( y[i] );
+ }
+
+ return result;
+} \ No newline at end of file
diff --git a/lib/tgamma.c b/lib/tgamma.c
new file mode 100644
index 0000000..967072e
--- /dev/null
+++ b/lib/tgamma.c
@@ -0,0 +1,42 @@
+// Tgamma
+//
+// tgamma
+//
+// Gamma function
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_tgamma_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_tgamma_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_tgamma(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_tgamma_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xv = x[0][i];
+ if( xv <= 0.0 ) { y[0][i] = 0; continue; }
+ y[0][i] = tgamma( xv );
+ }
+ return 0;
+}
+
diff --git a/lib/tick_volume.c b/lib/tick_volume.c
new file mode 100644
index 0000000..9e28ec7
--- /dev/null
+++ b/lib/tick_volume.c
@@ -0,0 +1,41 @@
+// Tick Volume
+//
+// tick_volume
+//
+// Tick Volume
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_tick_volume_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_tick_volume_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_tick_volume(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_tick_volume_p );
+ (void)c;
+ ( void ) x;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = 1;
+ }
+
+ return 0;
+}
+
diff --git a/lib/time_series_forecast.c b/lib/time_series_forecast.c
new file mode 100644
index 0000000..bf8e7af
--- /dev/null
+++ b/lib/time_series_forecast.c
@@ -0,0 +1,58 @@
+// Time Series Forecast
+//
+// time_series_forecast
+//
+// Time Series Forecast
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_time_series_forecast_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_time_series_forecast_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 4 );
+}
+
+int ts_time_series_forecast(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_time_series_forecast_p );
+ (void)c;
+ for ( uint_t j = 0; j < n && j < 4; ++j ) y[0][j] = 0;
+ for ( uint_t i = 4; i < n; ++i )
+ {
+ fp_t sum_x = 0, sum_y = 0, sum_xy = 0, sum_x2 = 0;
+ for ( uint_t j = 0; j < 5; ++j )
+ {
+ fp_t xv = (fp_t)j;
+ fp_t yv = x[0][i - 4 + j];
+ sum_x += xv;
+ sum_y += yv;
+ sum_xy += xv * yv;
+ sum_x2 += xv * xv;
+ }
+ fp_t denom = 5.0 * sum_x2 - sum_x * sum_x;
+ if (denom == 0) {
+ y[0][i] = sum_y / 5.0;
+ } else {
+ fp_t slope = (5.0 * sum_xy - sum_x * sum_y) / denom;
+ fp_t intercept = (sum_y - slope * sum_x) / 5.0;
+ y[0][i] = intercept + slope * 5.0;
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/trade_volume_index.c b/lib/trade_volume_index.c
new file mode 100644
index 0000000..2e1b50d
--- /dev/null
+++ b/lib/trade_volume_index.c
@@ -0,0 +1,43 @@
+// Trade Volume Index
+//
+// trade_volume_index
+//
+// Trade Volume Index
+//
+//
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_trade_volume_index_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_trade_volume_index_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_trade_volume_index(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+ )
+ {
+ TS_PROLOGUE( ts_trade_volume_index_p );
+ (void)c;
+ if (n < 2) return -1;
+ for( uint_t i = 0; i + 1 < n; ++i )
+ {
+ fp_t roc = x[3][i] != 0 ? ( x[3][i + 1] - x[3][i] ) / x[3][i] : 0;
+ y[0][i] = ( i == 0 ? 0 : y[0][i - 1] ) + roc * x[4][i + 1];
+ }
+ y[0][n - 1] = y[0][n - 2];
+ return 0;
+ }
+
diff --git a/lib/trend_intensity_index.c b/lib/trend_intensity_index.c
new file mode 100644
index 0000000..451e440
--- /dev/null
+++ b/lib/trend_intensity_index.c
@@ -0,0 +1,62 @@
+// Trend Intensity Index
+//
+// trend_intensity_index
+//
+// Trend Intensity Index
+//
+//
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_trend_intensity_index_params_t;
+
+ts_parameter_t const ts_trend_intensity_index_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_trend_intensity_index_params_t) };
+
+uint_t ts_trend_intensity_index_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_trend_intensity_index_params_t *)c)->k > 0 ? ((ts_trend_intensity_index_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_trend_intensity_index(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_trend_intensity_index_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_trend_intensity_index_params_t *)c)->k;
+ if ( k == 0 || n < k ) return -1;
+
+ fp_t signed_sum = 0;
+ fp_t vol_sum = 0;
+
+ for ( uint_t i = 1; i <= k; ++i )
+ {
+ signed_sum += ( ts_c( x, i ) > ts_c( x, i - 1 ) ) ? ts_v( x, i ) : -ts_v( x, i );
+ vol_sum += ts_v( x, i );
+ }
+
+ y[0][k - 1] = vol_sum != 0 ? signed_sum / vol_sum : 0;
+
+ for ( uint_t i = k + 1; i < n; ++i )
+ {
+ fp_t cur_signed = ( ts_c( x, i ) > ts_c( x, i - 1 ) ) ? ts_v( x, i ) : -ts_v( x, i );
+ fp_t old_signed = ( ts_c( x, i - k ) > ts_c( x, i - k - 1 ) ) ? ts_v( x, i - k ) : -ts_v( x, i - k );
+
+ signed_sum = signed_sum - old_signed + cur_signed;
+ vol_sum = vol_sum - ts_v( x, i - k ) + ts_v( x, i );
+
+ y[0][i] = vol_sum != 0 ? signed_sum / vol_sum : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/trix.c b/lib/trix.c
new file mode 100755
index 0000000..a9aac88
--- /dev/null
+++ b/lib/trix.c
@@ -0,0 +1,61 @@
+// Trix
+//
+// trix - trix
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+
+typedef struct {
+ fp_t a;
+} ts_trix_params_t;
+
+ts_parameter_t const ts_trix_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_trix_params_t) };
+
+
+uint_t ts_trix_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_trix(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_trix_p );
+ if ( !c ) return -1;
+ if (n == 0) return -1;
+
+ fp_t a = ((ts_trix_params_t *)c)->a;
+ if (a == 0) return -1;
+
+ fp_t ema1 = x[0][0];
+ fp_t ema2 = x[0][0];
+ fp_t ema30 = x[0][0];
+ fp_t ema31;
+
+ y[0][0] = x[0][0];
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ ema1 = ema1 + a * ( x[0][i] - ema1 );
+ ema2 = ema2 + a * ( ema1 - ema2 );
+ ema31 = ema30 + a * ( ema2 - ema30 );
+
+ if (ema30 == 0)
+ y[0][i] = 0;
+ else
+ y[0][i] = ( ema31 - ema30 ) / ema30;
+
+ ema30 = ema31;
+ }
+ return 0;
+}
+
diff --git a/lib/true_range.c b/lib/true_range.c
new file mode 100644
index 0000000..ff1740e
--- /dev/null
+++ b/lib/true_range.c
@@ -0,0 +1,45 @@
+// True Range
+//
+// true_range
+//
+// True Range
+//
+//
+//
+// Inputs: 4
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_true_range_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(uint_t) };
+
+
+uint_t ts_true_range_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 1 );
+}
+
+int ts_true_range(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_true_range_p );
+ (void)c;
+ if (n > 0) y[0][0] = 0;
+ for( uint_t i = 1; i < n; ++i )
+ {
+ y[0][i] = fmax( ( ts_h( x, i ) - ts_l( x, i ) ),
+ fmax( fabs( ts_h( x, i ) - ts_c( x, i - 1 ) ),
+ fabs( ts_l( x, i ) - ts_c( x, i - 1 ) ) ) );
+ }
+ return 0;
+}
+
diff --git a/lib/truncate.c b/lib/truncate.c
new file mode 100644
index 0000000..0b83feb
--- /dev/null
+++ b/lib/truncate.c
@@ -0,0 +1,40 @@
+// Truncate
+//
+// truncate - trunc
+//
+// f( x ) = [ x ]
+//
+// X_n - 1
+// Y_n - 1
+// C_n - 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_truncate_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_truncate_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_truncate(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_truncate_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = trunc( x[0][i] );
+ }
+ return 0;
+}
+
diff --git a/lib/ts.c b/lib/ts.c
new file mode 100755
index 0000000..68e2f41
--- /dev/null
+++ b/lib/ts.c
@@ -0,0 +1,119 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include "ts.h"
+
+
+fp_t * ts_fill( fp_t *x, uint_t n, fp_t x0, fp_t x1 )
+{
+ fp_t step = ( x1 - x0 ) / ( n - 1 );
+
+ if( x0 == 0.0 && x1 == 0.0 )
+ {
+ memset(x, 0, sizeof( fp_t ) * n );
+ }
+
+ else
+ {
+ for( uint_t i = 0; i < n; i++ )
+ {
+ x[i] = x0 + ( i * step );
+ }
+ }
+
+ return x;
+}
+
+
+void ts_print( fp_t *x, uint_t n )
+{
+ fputc('[', stdout);
+
+ uint_t i;
+ for( i = 0; i < n - 1; i++ )
+ {
+ fprintf(stdout, "%f,", x[i]);
+ }
+
+ fprintf(stdout, "%f]", x[i]);
+}
+
+
+void ts_pretty_print( fp_t const * const *x, uint_t m, uint_t n )
+{
+ if( m > 1 )
+ {
+ for( uint_t i = 0; i < n; i++ )
+ {
+ uint_t j = 0;
+ for( ; j < m; j++ )
+ {
+ fprintf( stdout, "%f ", x[i][j] );
+ }
+
+ fputs( "\n", stdout );
+ }
+ }
+ else
+ {
+ for( uint_t i = 0; i < n; i++ )
+ {
+ fprintf( stdout, "%f\n", x[0][i] );
+ }
+ }
+}
+
+
+fp_t _ts_square( fp_t x )
+{
+ return x * x;
+}
+
+
+fp_t _ts_reciprocal( fp_t x )
+{
+ return x != 0 ? 1.0 / x : INFINITY;
+}
+
+
+fp_t _ts_square_pyramidal_number( fp_t x )
+{
+ return ( x * ( x + 1 ) * ( 2 * x + 1 ) ) / 6;
+}
+
+
+fp_t _ts_triangle_number( fp_t x )
+{
+ return ( ( x ) * ( ( x ) + 1 ) ) * 0.5;
+}
+
+
+fp_t _ts_sum_to_covariance( fp_t sum_xy, fp_t sum_x, fp_t sum_y, uint_t k )
+{
+ return ( sum_xy - ( sum_x * sum_y / k ) ) / k;
+}
+
+
+fp_t _ts_sum_to_variance( fp_t sum_x2, fp_t sum_x, uint_t k )
+{
+ return _ts_sum_to_covariance( sum_x2, sum_x, sum_x, k );
+}
+
+
+uint_t _ts_circle_increment( uint_t i, uint_t k )
+{
+ return ( i + 1 ) % k;
+}
+
+
+// ts_constant_t * ts_constant_init( uint_t n, ... )
+// {
+// va_list args;
+// va_start( args, n );
+
+// for( uint_t i = 0; i < n; i++ )
+// {
+
+// }
+// } \ No newline at end of file
diff --git a/lib/twiggs_money_flow.c b/lib/twiggs_money_flow.c
new file mode 100644
index 0000000..334f968
--- /dev/null
+++ b/lib/twiggs_money_flow.c
@@ -0,0 +1,67 @@
+// Twiggs Money Flow
+//
+// twiggs_money_flow
+//
+// Twiggs Money Flow
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_twiggs_money_flow_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_twiggs_money_flow_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 35 );
+}
+
+int ts_twiggs_money_flow(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_twiggs_money_flow_p );
+ (void)c;
+ if (n < 2) return -1;
+
+ uint_t period = n - 2;
+
+ fp_t pos_sum = 0;
+ fp_t neg_sum = 0;
+
+ for( uint_t j = 0; j < period; ++j ) y[0][j] = 0;
+ for( uint_t i = 1; i <= period; ++i )
+ {
+ fp_t change = x[0][i] - x[0][i - 1];
+ if( change >= 0 ) pos_sum += change;
+ else neg_sum += fabs( change );
+ }
+
+ fp_t total = pos_sum + neg_sum;
+ y[0][period] = ( total != 0 ) ? ( pos_sum - neg_sum ) / total * 100.0 : 0;
+
+ for( uint_t j = 1; j < n - period; ++j )
+ {
+ fp_t new_change = x[0][period + j] - x[0][period + j - 1];
+ fp_t old_change = x[0][j] - x[0][j - 1];
+
+ if( new_change >= 0 ) pos_sum += new_change;
+ else neg_sum += fabs( new_change );
+ if( old_change >= 0 ) pos_sum -= old_change;
+ else neg_sum -= fabs( old_change );
+
+ total = pos_sum + neg_sum;
+ y[0][period + j] = ( total != 0 ) ? ( pos_sum - neg_sum ) / total * 100.0 : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/typical_price.c b/lib/typical_price.c
new file mode 100644
index 0000000..2d7f1c6
--- /dev/null
+++ b/lib/typical_price.c
@@ -0,0 +1,41 @@
+// Typical Price
+//
+// typical_price
+//
+// Typical Price
+//
+//
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_typical_price_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_typical_price_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_typical_price(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_typical_price_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = ( x[1][i] + x[2][i] + x[3][i] ) / 3;
+ }
+ return 0;
+}
+
diff --git a/lib/ulcer_index.c b/lib/ulcer_index.c
new file mode 100644
index 0000000..ca70734
--- /dev/null
+++ b/lib/ulcer_index.c
@@ -0,0 +1,76 @@
+// Ulcer Index
+//
+// ulcer_index
+//
+// Ulcer Index
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include <stdlib.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_ulcer_index_params_t;
+
+ts_parameter_t const ts_ulcer_index_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_ulcer_index_params_t) };
+
+uint_t ts_ulcer_index_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_ulcer_index_params_t *)c)->k > 0 ? ((ts_ulcer_index_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_ulcer_index(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_ulcer_index_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_ulcer_index_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ /* Precompute cumulative highest-high for each index: O(n) */
+ fp_t *cum_hhv = calloc( n, sizeof( fp_t ) );
+ if ( !cum_hhv ) return -1;
+ {
+ fp_t hh = x[0][0];
+ for ( uint_t i = 0; i < n; ++i )
+ {
+ if ( x[0][i] > hh ) hh = x[0][i];
+ cum_hhv[i] = hh;
+ }
+ }
+
+ for( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t hhv = x[0][i - k + 1];
+ for( uint_t j = 1; j < k; ++j )
+ {
+ fp_t v = x[0][i - k + 1 + j];
+ if (v > hhv) hhv = v;
+ }
+
+ fp_t d_sq_sum = 0;
+ for( uint_t j = 0; j < k; ++j )
+ {
+ uint_t idx = i - k + 1 + j;
+ fp_t ch = cum_hhv[idx];
+ fp_t d = ch > 0 ? ( ch - x[0][idx] ) / ch * 100.0 : 0;
+ d_sq_sum += d * d;
+ }
+ y[0][i] = sqrt( d_sq_sum / (fp_t)k );
+ }
+
+ free( cum_hhv );
+ return 0;
+}
+
diff --git a/lib/ultimate_oscillator.c b/lib/ultimate_oscillator.c
new file mode 100644
index 0000000..aec2ef4
--- /dev/null
+++ b/lib/ultimate_oscillator.c
@@ -0,0 +1,88 @@
+// Ultimate Oscillator
+//
+// ultimate_oscillator
+//
+// Ultimate Oscillator
+//
+//
+//
+// Inputs: 4
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+ts_parameter_t const ts_ultimate_oscillator_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_ultimate_oscillator_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 54 );
+}
+
+int ts_ultimate_oscillator(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_ultimate_oscillator_p );
+ (void)c;
+ for( uint_t i = 54; i < n; ++i )
+ {
+ fp_t bp7 = 0, tr7 = 0;
+ fp_t bp14 = 0, tr14 = 0;
+ fp_t bp28 = 0, tr28 = 0;
+
+ for( uint_t j = 0; j < 7; ++j )
+ {
+ uint_t idx = i - 6 + j;
+ fp_t ll = ts_l( x, idx );
+ fp_t hh = ts_h( x, idx );
+ for( uint_t k = 1; k < 7; ++k )
+ {
+ ll = fmin( ll, ts_l( x, idx - k ) );
+ hh = fmax( hh, ts_h( x, idx - k ) );
+ }
+ bp7 += ts_c( x, idx ) - ll;
+ tr7 += hh - ll;
+ }
+
+ for( uint_t j = 0; j < 14; ++j )
+ {
+ uint_t idx = i - 13 + j;
+ fp_t ll = ts_l( x, idx );
+ fp_t hh = ts_h( x, idx );
+ for( uint_t k = 1; k < 14; ++k )
+ {
+ ll = fmin( ll, ts_l( x, idx - k ) );
+ hh = fmax( hh, ts_h( x, idx - k ) );
+ }
+ bp14 += ts_c( x, idx ) - ll;
+ tr14 += hh - ll;
+ }
+
+ for( uint_t j = 0; j < 28; ++j )
+ {
+ uint_t idx = i - 27 + j;
+ fp_t ll = ts_l( x, idx );
+ fp_t hh = ts_h( x, idx );
+ for( uint_t k = 1; k < 28; ++k )
+ {
+ ll = fmin( ll, ts_l( x, idx - k ) );
+ hh = fmax( hh, ts_h( x, idx - k ) );
+ }
+ bp28 += ts_c( x, idx ) - ll;
+ tr28 += hh - ll;
+ }
+
+ fp_t denom = 7.0 * tr7 + 3.0 * tr14 + tr28;
+ fp_t numer = 7.0 * bp7 + 3.0 * bp14 + bp28;
+ y[0][i] = denom != 0 ? 100.0 * numer / denom : 50.0;
+ }
+ return 0;
+}
+
diff --git a/lib/valuation_lines.c b/lib/valuation_lines.c
new file mode 100644
index 0000000..b56a871
--- /dev/null
+++ b/lib/valuation_lines.c
@@ -0,0 +1,62 @@
+// Valuation Lines
+//
+// valuation_lines
+//
+// Valuation Lines
+//
+//
+//
+// Inputs: 4
+// Outputs: 2
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_valuation_lines_params_t;
+
+ts_parameter_t const ts_valuation_lines_p = { .n_inputs = 4, .n_outputs = 2, .n_constants = 1, .params_size = sizeof(ts_valuation_lines_params_t) };
+
+uint_t ts_valuation_lines_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_valuation_lines_params_t *)c)->k > 0 ? ((ts_valuation_lines_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_valuation_lines(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_valuation_lines_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_valuation_lines_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for( uint_t i = k - 1; i < n; ++i )
+ {
+ fp_t min_val = ts_l( x, i );
+ for( uint_t j = 1; j < k; ++j )
+ {
+ fp_t val = ts_l( x, i - j );
+ if( val < min_val )
+ min_val = val;
+ }
+ y[0][i] = min_val;
+
+ fp_t max_val = ts_h( x, i );
+ for( uint_t j = 1; j < k; ++j )
+ {
+ fp_t val = ts_h( x, i - j );
+ if( val > max_val )
+ max_val = val;
+ }
+ y[1][i] = max_val;
+ }
+ return 0;
+}
+
diff --git a/lib/variable_index_dynamic_average.c b/lib/variable_index_dynamic_average.c
new file mode 100644
index 0000000..5f4ae4f
--- /dev/null
+++ b/lib/variable_index_dynamic_average.c
@@ -0,0 +1,65 @@
+// Variable Index Dynamic Average
+//
+// variable_index_dynamic_average
+//
+// Variable Index Dynamic Average
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t period;
+} ts_variable_index_dynamic_average_params_t;
+
+ts_parameter_t const ts_variable_index_dynamic_average_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_variable_index_dynamic_average_params_t) };
+
+uint_t ts_variable_index_dynamic_average_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_variable_index_dynamic_average_params_t *)c)->period : 0 );
+}
+
+int ts_variable_index_dynamic_average(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_variable_index_dynamic_average_p );
+ if ( !c ) return -1;
+ uint_t period = ((ts_variable_index_dynamic_average_params_t *)c)->period;
+ if (period < 2 || n <= period) return -1;
+
+ fp_t vidya = x[0][period];
+ y[0][period] = vidya;
+
+ fp_t alpha_init = (fp_t)(period - 2) / (2.0 * (fp_t)(period - 1));
+ fp_t alpha = alpha_init;
+
+ for( uint_t i = period + 1; i < n; ++i )
+ {
+ vidya = vidya + alpha * (x[0][i] - vidya);
+ y[0][i] = vidya;
+
+ fp_t numerator = fabs( x[0][i] - x[0][i - period] );
+ fp_t denominator = 0;
+ for( uint_t j = i - period + 1; j <= i; ++j )
+ denominator += fabs( x[0][j] - x[0][j - 1] );
+ fp_t er = (denominator == 0) ? 0 : numerator / denominator;
+
+ fp_t alpha_new = er * er * 2.0 / (fp_t)(period + 1);
+ if( alpha_new < 0 ) alpha_new = 0;
+ if( alpha_new > 1 ) alpha_new = 1;
+
+ alpha = (alpha + alpha_new) / 2.0;
+ }
+ return 0;
+}
+
diff --git a/lib/variance.c b/lib/variance.c
new file mode 100755
index 0000000..f307198
--- /dev/null
+++ b/lib/variance.c
@@ -0,0 +1,72 @@
+// Variance
+//
+// variance - smv
+//
+// Calculates the moving variance
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+typedef struct {
+ uint_t k;
+} ts_variance_params_t;
+
+ts_parameter_t const ts_variance_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_variance_params_t) };
+
+
+uint_t ts_variance_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_variance_params_t *)c)->k > 0 ? ((ts_variance_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_variance(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_variance_p );
+ if ( !c ) return -1;
+ TS_CHECK_INPUT(x, x_n, n);
+ uint_t k = ((ts_variance_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ uint_t xi = 0;
+ uint_t yi = k;
+
+ fp_t sum = 0; // Sum of terms
+ fp_t sum2 = 0; // Sum of squared terms
+ fp_t mean; // Mean
+ fp_t mean2; // Mean of squares
+
+ for( ; xi < k; ++xi )
+ {
+ sum += x[0][xi];
+ sum2 += _ts_square( x[0][xi] );
+
+ mean = sum / ( xi + 1 );
+ mean2 = sum2 / ( xi + 1 );
+ }
+
+ fp_t var = mean2 - _ts_square( mean );
+ y[0][k - 1] = var >= 0 ? var : 0.0;
+
+ fp_t rk = _ts_reciprocal( k );
+
+ for( ; xi < n; ++xi, ++yi )
+ {
+ mean = mean + ( x[0][xi] - x[0][xi - k] ) * rk;
+ mean2 = mean2 + ( _ts_square( x[0][xi] ) - _ts_square( x[0][xi - k] ) ) * rk;
+
+ var = mean2 - _ts_square( mean );
+ y[0][yi] = var >= 0 ? var : 0.0;
+ }
+ return 0;
+}
+
diff --git a/lib/variance_exponential.c b/lib/variance_exponential.c
new file mode 100755
index 0000000..338a140
--- /dev/null
+++ b/lib/variance_exponential.c
@@ -0,0 +1,51 @@
+// Exponential Variance
+//
+// variance_exponential
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+#include "ts.h"
+
+
+typedef struct {
+ fp_t a;
+} ts_variance_exponential_params_t;
+
+ts_parameter_t const ts_variance_exponential_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_variance_exponential_params_t) };
+
+
+uint_t ts_variance_exponential_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_variance_exponential(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_variance_exponential_p );
+ if ( !c ) return -1;
+ if (n == 0) return -1;
+
+ fp_t a = ((ts_variance_exponential_params_t *)c)->a;
+ if (a == 0) return -1;
+
+ y[0][0] = 0;
+
+ fp_t delta;
+ fp_t ema = x[0][0];
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ delta = x[0][i] - ema;
+ ema = ema + a * delta;
+ y[0][i] = ( 1 - a ) * ( y[0][i - 1] + a * _ts_square( delta ) );
+ }
+ return 0;
+}
+
diff --git a/lib/vertical_horizontal_filter.c b/lib/vertical_horizontal_filter.c
new file mode 100644
index 0000000..f5c91cf
--- /dev/null
+++ b/lib/vertical_horizontal_filter.c
@@ -0,0 +1,75 @@
+// Vertical Horizontal Filter
+//
+// vertical_horizontal_filter
+//
+// Vertical Horizontal Filter
+//
+//
+//
+// Inputs: 4
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_vertical_horizontal_filter_params_t;
+
+ts_parameter_t const ts_vertical_horizontal_filter_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_vertical_horizontal_filter_params_t) };
+
+uint_t ts_vertical_horizontal_filter_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_vertical_horizontal_filter_params_t *)c)->k : 0 );
+}
+
+int ts_vertical_horizontal_filter(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_vertical_horizontal_filter_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_vertical_horizontal_filter_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ fp_t cum_den = 0;
+
+ for ( uint_t j = 1; j <= k; ++j )
+ {
+ cum_den += fabs( ts_h( x, j ) - ts_h( x, j - 1 ) )
+ + fabs( ts_l( x, j ) - ts_l( x, j - 1 ) );
+ }
+
+ for ( uint_t i = k; i < n; ++i )
+ {
+ if ( i > k )
+ {
+ cum_den += fabs( ts_h( x, i ) - ts_h( x, i - 1 ) )
+ + fabs( ts_l( x, i ) - ts_l( x, i - 1 ) );
+ cum_den -= fabs( ts_h( x, i - k ) - ts_h( x, i - k - 1 ) )
+ + fabs( ts_l( x, i - k ) - ts_l( x, i - k - 1 ) );
+ }
+
+ fp_t hh = ts_h( x, i - k + 1 );
+ fp_t ll = ts_l( x, i - k + 1 );
+ for ( uint_t j = i - k + 2; j <= i; ++j )
+ {
+ if ( ts_h( x, j ) > hh ) hh = ts_h( x, j );
+ if ( ts_l( x, j ) < ll ) ll = ts_l( x, j );
+ }
+
+ fp_t range = hh - ll;
+
+ if ( cum_den == 0 )
+ y[0][i] = ( range == 0 ) ? 1 : 0;
+ else
+ y[0][i] = range / cum_den;
+ }
+ return 0;
+}
+
diff --git a/lib/volatility_stop.c b/lib/volatility_stop.c
new file mode 100644
index 0000000..bfb207b
--- /dev/null
+++ b/lib/volatility_stop.c
@@ -0,0 +1,74 @@
+// Volatility Stop
+//
+// volatility_stop
+//
+// Volatility Stop
+//
+//
+//
+// Inputs: 4
+// Outputs: 2
+// Constants: 2
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+ fp_t mult;
+} ts_volatility_stop_params_t;
+
+ts_parameter_t const ts_volatility_stop_p = { .n_inputs = 4, .n_outputs = 2, .n_constants = 2, .params_size = sizeof(ts_volatility_stop_params_t) };
+
+uint_t ts_volatility_stop_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_volatility_stop_params_t *)c)->k : 0 );
+}
+
+int ts_volatility_stop(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_volatility_stop_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_volatility_stop_params_t *)c)->k;
+ fp_t mult = ((ts_volatility_stop_params_t *)c)->mult;
+ if (k == 0 || n < k) return -1;
+
+ for( uint_t i = k; i < n; ++i )
+ {
+ fp_t hh = ts_h(x, i - k);
+ fp_t ll = ts_l(x, i - k);
+
+ for( uint_t j = i - k + 1; j <= i; ++j )
+ {
+ if( ts_h(x, j) > hh ) hh = ts_h(x, j);
+ if( ts_l(x, j) < ll ) ll = ts_l(x, j);
+ }
+
+ fp_t atr = 0;
+ for( uint_t j = i - k; j <= i; ++j )
+ {
+ fp_t tr = ts_h(x, j) - ts_l(x, j);
+ if( j > 0 )
+ {
+ fp_t prev_close = ts_c(x, j - 1);
+ fp_t tr2 = fabs(ts_h(x, j) - prev_close);
+ fp_t tr3 = fabs(ts_l(x, j) - prev_close);
+ if( tr2 > tr ) tr = tr2;
+ if( tr3 > tr ) tr = tr3;
+ }
+ atr += tr;
+ }
+ atr /= (k + 1);
+
+ y[0][i] = hh - mult * atr;
+ y[1][i] = ll + mult * atr;
+ }
+ return 0;
+}
+
diff --git a/lib/volatility_system.c b/lib/volatility_system.c
new file mode 100644
index 0000000..dbb74d1
--- /dev/null
+++ b/lib/volatility_system.c
@@ -0,0 +1,55 @@
+// Volatility System
+//
+// volatility_system
+//
+// Volatility System
+//
+//
+//
+// Inputs: 4
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_volatility_system_params_t;
+
+ts_parameter_t const ts_volatility_system_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_volatility_system_params_t) };
+
+uint_t ts_volatility_system_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? 2 * ((ts_volatility_system_params_t *)c)->k : 0 );
+}
+
+int ts_volatility_system(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_volatility_system_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_volatility_system_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ uint_t range_period = 2 * k;
+ if (n < range_period) return -1;
+
+ fp_t ema = (ts_c(x, range_period - 2) + ts_c(x, range_period - 1)) / 2.0;
+
+ for( uint_t i = range_period; i < n; ++i )
+ {
+ if( i == range_period )
+ ema = (ts_c(x, i-1) + ts_c(x, i)) / 2.0;
+ else
+ ema = ema * 0.5 + ts_c(x, i) * 0.5;
+ y[0][i] = ema;
+ }
+ return 0;
+}
+
diff --git a/lib/volume_delta.c b/lib/volume_delta.c
new file mode 100644
index 0000000..cabd94a
--- /dev/null
+++ b/lib/volume_delta.c
@@ -0,0 +1,60 @@
+// Volume Delta
+//
+// volume_delta
+//
+// Volume Delta
+//
+//
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_volume_delta_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_volume_delta_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_volume_delta(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_volume_delta_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t hl = x[1][i] - x[2][i];
+ if( hl == 0 )
+ {
+ y[0][i] = 0;
+ }
+ else if( x[3][i] > x[0][i] )
+ {
+ fp_t buy_vol = x[4][i] * ( x[3][i] - x[2][i] ) / hl;
+ fp_t sell_vol = x[4][i] - buy_vol;
+ y[0][i] = buy_vol - sell_vol;
+ }
+ else if( x[3][i] < x[0][i] )
+ {
+ fp_t sell_vol = x[4][i] * ( x[1][i] - x[3][i] ) / hl;
+ fp_t buy_vol = x[4][i] - sell_vol;
+ y[0][i] = buy_vol - sell_vol;
+ }
+ else
+ {
+ y[0][i] = 0;
+ }
+ }
+
+ return 0;
+}
+
diff --git a/lib/volume_moving_average.c b/lib/volume_moving_average.c
new file mode 100644
index 0000000..93eb592
--- /dev/null
+++ b/lib/volume_moving_average.c
@@ -0,0 +1,48 @@
+// Volume Moving Average
+//
+// volume_moving_average
+//
+// Volume Moving Average
+//
+//
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_volume_moving_average_params_t;
+
+ts_parameter_t const ts_volume_moving_average_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_volume_moving_average_params_t) };
+
+uint_t ts_volume_moving_average_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_volume_moving_average_params_t *)c)->k > 0 ? ((ts_volume_moving_average_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_volume_moving_average(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_volume_moving_average_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_volume_moving_average_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for (uint_t i = k - 1; i < n; ++i)
+ {
+ fp_t sum = 0;
+ for (uint_t j = i - k + 1; j <= i; ++j)
+ sum += x[4][j];
+ y[0][i] = sum / k;
+ }
+ return 0;
+}
+
diff --git a/lib/volume_oscillator.c b/lib/volume_oscillator.c
new file mode 100644
index 0000000..6aa59f0
--- /dev/null
+++ b/lib/volume_oscillator.c
@@ -0,0 +1,57 @@
+// Volume Oscillator
+//
+// volume_oscillator
+//
+// Volume Oscillator
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 2
+
+
+#include "ts.h"
+
+typedef struct {
+ fp_t fast_k;
+ fp_t slow_k;
+} ts_volume_oscillator_params_t;
+
+ts_parameter_t const ts_volume_oscillator_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 2, .params_size = sizeof(ts_volume_oscillator_params_t) };
+
+uint_t ts_volume_oscillator_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_volume_oscillator_params_t *)c)->slow_k > 0 ? (uint_t)(((ts_volume_oscillator_params_t *)c)->slow_k - 1) : 0 : 0 );
+}
+
+int ts_volume_oscillator(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_volume_oscillator_p );
+ if ( !c ) return -1;
+ fp_t fast_k = ((ts_volume_oscillator_params_t *)c)->fast_k;
+ fp_t slow_k = ((ts_volume_oscillator_params_t *)c)->slow_k;
+ if (fast_k == 0 || slow_k == 0) return -1;
+ if (n == 0) return -1;
+
+ fp_t fast_a = 2.0 / (fast_k + 1);
+ fp_t slow_a = 2.0 / (slow_k + 1);
+
+ fp_t fast_ema = x[0][0];
+ fp_t slow_ema = x[0][0];
+ y[0][0] = 0;
+
+ for (uint_t i = 1; i < n; ++i)
+ {
+ fast_ema = fast_ema + fast_a * (x[0][i] - fast_ema);
+ slow_ema = slow_ema + slow_a * (x[0][i] - slow_ema);
+ y[0][i] = slow_ema != 0 ? (fast_ema - slow_ema) / slow_ema * 100 : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/volume_rate_of_change.c b/lib/volume_rate_of_change.c
new file mode 100644
index 0000000..ae9c776
--- /dev/null
+++ b/lib/volume_rate_of_change.c
@@ -0,0 +1,46 @@
+// Volume Rate Of Change
+//
+// volume_rate_of_change
+//
+// Volume Rate of Change
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_volume_rate_of_change_params_t;
+
+ts_parameter_t const ts_volume_rate_of_change_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_volume_rate_of_change_params_t) };
+
+uint_t ts_volume_rate_of_change_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_volume_rate_of_change_params_t *)c)->k : 0 );
+}
+
+int ts_volume_rate_of_change(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_volume_rate_of_change_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_volume_rate_of_change_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for (uint_t i = k; i < n; ++i)
+ {
+ y[0][i] = x[4][i - k] != 0 ? (x[4][i] - x[4][i - k]) / x[4][i - k] * 100 : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/volume_underlay.c b/lib/volume_underlay.c
new file mode 100644
index 0000000..030ba66
--- /dev/null
+++ b/lib/volume_underlay.c
@@ -0,0 +1,45 @@
+// Volume Underlay
+//
+// volume_underlay
+//
+// Volume Underlay
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_volume_underlay_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(uint_t) };
+
+uint_t ts_volume_underlay_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_volume_underlay(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_volume_underlay_p );
+ (void)c;
+ /* Volume Underlay: normalize volume to [0,1] */
+ /* Find max volume */
+ fp_t max_vol = 0;
+ for (uint_t i = 0; i < n; ++i) {
+ if (x[0][i] > max_vol) max_vol = x[0][i];
+ }
+
+ for (uint_t i = 0; i < n; ++i) {
+ y[0][i] = max_vol != 0 ? x[0][i] / max_vol : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/volume_weighted_moving_average.c b/lib/volume_weighted_moving_average.c
new file mode 100644
index 0000000..d6cd6bd
--- /dev/null
+++ b/lib/volume_weighted_moving_average.c
@@ -0,0 +1,58 @@
+// Volume Weighted Moving Average
+//
+// volume_weighted_moving_average
+//
+// Volume Weighted Moving Average
+//
+//
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_volume_weighted_moving_average_params_t;
+
+ts_parameter_t const ts_volume_weighted_moving_average_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_volume_weighted_moving_average_params_t) };
+
+uint_t ts_volume_weighted_moving_average_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_volume_weighted_moving_average_params_t *)c)->k > 0 ? ((ts_volume_weighted_moving_average_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_volume_weighted_moving_average(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_volume_weighted_moving_average_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_volume_weighted_moving_average_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ fp_t cv_sum = 0;
+ fp_t v_sum = 0;
+
+ for( uint_t j = 0; j < k; ++j )
+ {
+ cv_sum += ts_c(x, j) * ts_v(x, j);
+ v_sum += ts_v(x, j);
+ }
+
+ y[0][k - 1] = (v_sum != 0) ? cv_sum / v_sum : 0;
+
+ for( uint_t i = k; i < n; ++i )
+ {
+ cv_sum += ts_c(x, i) * ts_v(x, i) - ts_c(x, i - k) * ts_v(x, i - k);
+ v_sum += ts_v(x, i) - ts_v(x, i - k);
+ y[0][i] = (v_sum != 0) ? cv_sum / v_sum : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/vortex_indicator.c b/lib/vortex_indicator.c
new file mode 100644
index 0000000..39ecfbc
--- /dev/null
+++ b/lib/vortex_indicator.c
@@ -0,0 +1,64 @@
+// Vortex Indicator
+//
+// vortex_indicator
+//
+// Vortex Indicator
+//
+//
+//
+// Inputs: 4
+// Outputs: 2
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_vortex_indicator_params_t;
+
+ts_parameter_t const ts_vortex_indicator_p = { .n_inputs = 4, .n_outputs = 2, .n_constants = 1, .params_size = sizeof(ts_vortex_indicator_params_t) };
+
+uint_t ts_vortex_indicator_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_vortex_indicator_params_t *)c)->k : 0 );
+}
+
+int ts_vortex_indicator(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_vortex_indicator_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_vortex_indicator_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ for( uint_t i = k; i < n; ++i )
+ {
+ fp_t sum_vm_plus = 0;
+ fp_t sum_vm_minus = 0;
+ fp_t sum_tr = 0;
+
+ for( uint_t j = 0; j < k; ++j )
+ {
+ uint_t idx = i - k + 1 + j;
+ fp_t vm_plus = fabs( ts_h( x, idx ) - ts_l( x, idx - 1 ) );
+ fp_t vm_minus = fabs( ts_l( x, idx ) - ts_h( x, idx - 1 ) );
+ fp_t tr = fmax( ts_h( x, idx ) - ts_l( x, idx ),
+ fmax( fabs( ts_h( x, idx ) - ts_c( x, idx - 1 ) ),
+ fabs( ts_l( x, idx ) - ts_c( x, idx - 1 ) ) ) );
+ sum_vm_plus += vm_plus;
+ sum_vm_minus += vm_minus;
+ sum_tr += tr;
+ }
+
+ y[0][i] = sum_tr != 0 ? sum_vm_plus / sum_tr : 0;
+ y[1][i] = sum_tr != 0 ? sum_vm_minus / sum_tr : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/vwap.c b/lib/vwap.c
new file mode 100644
index 0000000..50308c7
--- /dev/null
+++ b/lib/vwap.c
@@ -0,0 +1,44 @@
+// Vwap
+//
+// vwap
+//
+// Volume Weighted Average Price
+//
+//
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_vwap_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_vwap_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_vwap(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_vwap_p );
+ (void)c;
+ fp_t cum_pv = 0;
+ fp_t cum_v = 0;
+
+ for( uint_t i = 0; i < n; ++i )
+ {
+ cum_pv += ts_c( x, i ) * ts_v( x, i );
+ cum_v += ts_v( x, i );
+ y[0][i] = cum_v != 0 ? cum_pv / cum_v : 0;
+ }
+ return 0;
+}
+
diff --git a/lib/vwap_period.c b/lib/vwap_period.c
new file mode 100644
index 0000000..b8af2bd
--- /dev/null
+++ b/lib/vwap_period.c
@@ -0,0 +1,58 @@
+// Vwap Period
+//
+// vwap_period
+//
+// Period VWAP
+//
+//
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_vwap_period_params_t;
+
+ts_parameter_t const ts_vwap_period_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_vwap_period_params_t) };
+
+uint_t ts_vwap_period_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_vwap_period_params_t *)c)->k > 0 ? ((ts_vwap_period_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_vwap_period(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_vwap_period_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_vwap_period_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ /* Periodic VWAP: reset cumulative sums every k bars */
+ fp_t sum_pv = 0;
+ fp_t sum_vol = 0;
+
+ for (uint_t i = 0; i < n; ++i)
+ {
+ sum_pv += ts_c( x, i ) * ts_v( x, i );
+ sum_vol += ts_v( x, i );
+
+ y[0][i] = sum_vol != 0 ? sum_pv / sum_vol : 0;
+
+ if ((i + 1) % k == 0 || i == n - 1)
+ {
+ sum_pv = 0;
+ sum_vol = 0;
+ }
+ }
+ return 0;
+}
+
diff --git a/lib/weibull_pdf.c b/lib/weibull_pdf.c
new file mode 100644
index 0000000..894c94a
--- /dev/null
+++ b/lib/weibull_pdf.c
@@ -0,0 +1,46 @@
+// Weibull Pdf
+//
+// weibull_pdf
+//
+// Weibull probability density
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include <math.h>
+#include "ts.h"
+
+typedef struct {
+ fp_t k;
+} ts_weibull_pdf_params_t;
+
+ts_parameter_t const ts_weibull_pdf_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_weibull_pdf_params_t) };
+
+uint_t ts_weibull_pdf_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_weibull_pdf(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_weibull_pdf_p );
+ if ( !c ) return -1;
+ fp_t k = ((ts_weibull_pdf_params_t *)c)->k;
+ if (k <= 0.0 || isnan(k) || isinf(k)) return -1;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t xi = x[0][i];
+ y[0][i] = xi >= 0 ? k * pow( xi, k - 1.0 ) * exp( -pow( xi, k ) ) : 0.0;
+ }
+ return 0;
+}
+
diff --git a/lib/weighted_close.c b/lib/weighted_close.c
new file mode 100644
index 0000000..30b8585
--- /dev/null
+++ b/lib/weighted_close.c
@@ -0,0 +1,39 @@
+// Weighted Close
+//
+// weighted_close
+//
+// Weighted Close
+//
+//
+//
+// Inputs: 5
+// Outputs: 1
+// Constants: 0
+
+
+#include "ts.h"
+
+
+ts_parameter_t const ts_weighted_close_p = { .n_inputs = 5, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+uint_t ts_weighted_close_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_weighted_close(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_weighted_close_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ y[0][i] = ( x[1][i] + x[2][i] + 2 * x[3][i] ) / 4;
+ }
+ return 0;
+}
+
diff --git a/lib/wilders_smoothing.c b/lib/wilders_smoothing.c
new file mode 100644
index 0000000..7aca8ad
--- /dev/null
+++ b/lib/wilders_smoothing.c
@@ -0,0 +1,52 @@
+// Wilders Smoothing
+//
+// wilders_smoothing
+//
+// Wilder's Smoothing
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ fp_t k;
+} ts_wilders_smoothing_params_t;
+
+ts_parameter_t const ts_wilders_smoothing_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_wilders_smoothing_params_t) };
+
+uint_t ts_wilders_smoothing_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_wilders_smoothing(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_wilders_smoothing_p );
+ if ( !c ) return -1;
+ if (n == 0) return -1;
+
+ fp_t k = ((ts_wilders_smoothing_params_t *)c)->k;
+ if (k == 0) return -1;
+ fp_t a = 1.0 / k;
+
+ y[0][0] = x[0][0];
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ y[0][i] = y[0][i - 1] + a * ( x[0][i] - y[0][i - 1] );
+ }
+
+ return 0;
+}
+
+
diff --git a/lib/williams_r.c b/lib/williams_r.c
new file mode 100644
index 0000000..f0fae3f
--- /dev/null
+++ b/lib/williams_r.c
@@ -0,0 +1,86 @@
+// Williams R
+//
+// williams_r
+//
+// Williams %R
+//
+//
+//
+// Inputs: 4
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_williams_r_params_t;
+
+ts_parameter_t const ts_williams_r_p = { .n_inputs = 4, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_williams_r_params_t) };
+
+uint_t ts_williams_r_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_williams_r_params_t *)c)->k > 0 ? ((ts_williams_r_params_t *)c)->k - 1 : 0 : 0 );
+}
+
+int ts_williams_r(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_williams_r_p );
+ if ( !c ) return -1;
+ TS_CHECK_INPUT(x, x_n, n);
+ uint_t k = ((ts_williams_r_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ // Initial window max/min
+ fp_t hh = ts_h( x, 0 );
+ fp_t ll = ts_l( x, 0 );
+ for( uint_t j = 1; j < k; ++j )
+ {
+ if( ts_h( x, j ) > hh )
+ hh = ts_h( x, j );
+ if( ts_l( x, j ) < ll )
+ ll = ts_l( x, j );
+ }
+
+ {
+ if( hh == ll )
+ y[0][k - 1] = 0;
+ else
+ y[0][k - 1] = (hh - ts_c( x, k - 1 )) / (hh - ll) * (-100.0);
+ }
+
+ for( uint_t i = k; i < n; ++i )
+ {
+ fp_t new_h = ts_h( x, i );
+ fp_t new_l = ts_l( x, i );
+ fp_t old_h = ts_h( x, i - k );
+ fp_t old_l = ts_l( x, i - k );
+
+ if ( new_h > hh ) hh = new_h;
+ if ( new_l < ll ) ll = new_l;
+ if ( old_h == hh || old_l == ll )
+ {
+ hh = ts_h( x, i - k + 1 );
+ ll = ts_l( x, i - k + 1 );
+ for( uint_t j = i - k + 2; j <= i; ++j )
+ {
+ if( ts_h( x, j ) > hh ) hh = ts_h( x, j );
+ if( ts_l( x, j ) < ll ) ll = ts_l( x, j );
+ }
+ }
+
+ if( hh == ll )
+ y[0][i] = 0;
+ else
+ y[0][i] = (hh - ts_c( x, i )) / (hh - ll) * (-100.0);
+ }
+
+ return 0;
+}
+
diff --git a/lib/zero_lag_ema.c b/lib/zero_lag_ema.c
new file mode 100644
index 0000000..c78adbd
--- /dev/null
+++ b/lib/zero_lag_ema.c
@@ -0,0 +1,53 @@
+// Zero Lag Ema
+//
+// zero_lag_ema
+//
+// Zero Lag EMA
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 1
+
+
+#include "ts.h"
+
+typedef struct {
+ uint_t k;
+} ts_zero_lag_ema_params_t;
+
+ts_parameter_t const ts_zero_lag_ema_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 1, .params_size = sizeof(ts_zero_lag_ema_params_t) };
+
+uint_t ts_zero_lag_ema_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( c ? ((ts_zero_lag_ema_params_t *)c)->k : 0 );
+}
+
+int ts_zero_lag_ema(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_zero_lag_ema_p );
+ if ( !c ) return -1;
+ uint_t k = ((ts_zero_lag_ema_params_t *)c)->k;
+ if (k == 0 || n < k) return -1;
+
+ /* Zero-lag EMA: compute lag, then apply EMA to (2*x - x_lag) */
+ fp_t alpha = 2.0 / (k + 1.0);
+ uint_t lag = (k - 1) / 2;
+
+ for (uint_t i = lag; i < n; ++i)
+ {
+ fp_t adjusted = 2.0 * x[0][i] - x[0][i - lag];
+ if (i == lag)
+ y[0][i] = adjusted;
+ else
+ y[0][i] = y[0][i - 1] + alpha * (adjusted - y[0][i - 1]);
+ }
+ return 0;
+}
+
diff --git a/lib/zeta.c b/lib/zeta.c
new file mode 100644
index 0000000..7b32e77
--- /dev/null
+++ b/lib/zeta.c
@@ -0,0 +1,52 @@
+// Zeta
+//
+// zeta
+//
+// Riemann Zeta Function
+//
+//
+//
+// Inputs: 1
+// Outputs: 1
+// Constants: 0
+
+
+#include <math.h>
+#include "ts.h"
+
+
+
+ts_parameter_t const ts_zeta_p = { .n_inputs = 1, .n_outputs = 1, .n_constants = 0, .params_size = 0 };
+
+
+uint_t ts_zeta_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_zeta(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_zeta_p );
+ (void)c;
+ for( uint_t i = 0; i < n; ++i )
+ {
+ fp_t s = x[0][i];
+ if( s <= 1.0 ) { y[0][i] = 0; continue; }
+ fp_t sum = 0.0;
+ fp_t term = 0.0;
+ for( uint_t k = 1; k <= 100000; ++k )
+ {
+ term = 1.0 / pow( (fp_t)k, s );
+ sum += term;
+ if( term < 1e-12 && k > 1000 ) break;
+ }
+ y[0][i] = sum;
+ }
+ return 0;
+}
+
diff --git a/lib/zigzag.c b/lib/zigzag.c
new file mode 100644
index 0000000..50549e2
--- /dev/null
+++ b/lib/zigzag.c
@@ -0,0 +1,99 @@
+// Zigzag
+//
+// zigzag
+//
+// ZigZag Filter
+//
+//
+//
+// Inputs: 1
+// Outputs: 2
+// Constants: 1
+
+
+#include "ts.h"
+
+
+typedef struct {
+ fp_t threshold;
+} ts_zigzag_params_t;
+
+ts_parameter_t const ts_zigzag_p = { .n_inputs = 1, .n_outputs = 2, .n_constants = 1, .params_size = sizeof(ts_zigzag_params_t) };
+
+
+uint_t ts_zigzag_offset( ts_constant_t const *c )
+{
+ ( void )c;
+ return ( 0 );
+}
+
+int ts_zigzag(
+ fp_t * const *y, uint_t y_n,
+ fp_t const * const *x, uint_t x_n,
+ uint_t n, ts_constant_t const *c
+)
+{
+ TS_PROLOGUE( ts_zigzag_p );
+ if ( !c ) return -1;
+ if (n == 0) return -1;
+
+ fp_t threshold = ((ts_zigzag_params_t *)c)->threshold;
+ if (threshold <= 0 || isnan(threshold) || isinf(threshold)) return -1;
+
+ y[0][0] = x[0][0];
+ y[1][0] = 0;
+
+ for( uint_t i = 1; i < n; ++i )
+ {
+ fp_t last_zz = y[0][i - 1];
+ fp_t dir = y[1][i - 1];
+ fp_t change = last_zz != 0 ? ( x[0][i] - last_zz ) / last_zz * 100.0 : 0;
+
+ if( dir == 0 )
+ {
+ if( change >= threshold )
+ {
+ y[0][i] = x[0][i];
+ y[1][i] = 1;
+ }
+ else if( change <= -threshold )
+ {
+ y[0][i] = x[0][i];
+ y[1][i] = -1;
+ }
+ else
+ {
+ y[0][i] = last_zz;
+ y[1][i] = 0;
+ }
+ }
+ else if( dir == 1 )
+ {
+ if( change <= -threshold )
+ {
+ y[0][i] = x[0][i];
+ y[1][i] = -1;
+ }
+ else
+ {
+ y[0][i] = last_zz;
+ y[1][i] = 1;
+ }
+ }
+ else
+ {
+ if( change >= threshold )
+ {
+ y[0][i] = x[0][i];
+ y[1][i] = 1;
+ }
+ else
+ {
+ y[0][i] = last_zz;
+ y[1][i] = -1;
+ }
+ }
+ }
+ return 0;
+}
+
diff --git a/main.c b/main.c
new file mode 100755
index 0000000..4308e55
--- /dev/null
+++ b/main.c
@@ -0,0 +1,92 @@
+// main.c - Moving average calculator using ts library
+// Computes a simple moving average over configurable period and displays results.
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define TS_IMPLEMENTATION
+#include "ts.h"
+
+#define BUFFER_SIZE 100
+#define DISPLAY_N 20
+
+static void usage(const char *prog)
+{
+ fprintf(stderr, "Usage: %s [period]\n", prog);
+ fprintf(stderr, " period: moving average window (1-%d, default: 10)\n", BUFFER_SIZE);
+}
+
+int main(int argc, char *argv[]) {
+ uint_t period = 10;
+ if (argc > 1 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)) {
+ usage(argv[0]);
+ return 0;
+ }
+ if (argc > 1) {
+ char *endptr = NULL;
+ errno = 0;
+ long val = strtol(argv[1], &endptr, 10);
+ if (endptr == argv[1] || *endptr != '\0' || errno == ERANGE) {
+ fprintf(stderr, "Error: invalid period '%s'\n", argv[1]);
+ usage(argv[0]);
+ return 1;
+ }
+ if (val < 1 || val > BUFFER_SIZE) {
+ fprintf(stderr, "Error: period must be between 1 and %d\n", BUFFER_SIZE);
+ usage(argv[0]);
+ return 1;
+ }
+ period = (uint_t)val;
+ }
+ if (period == 0 || period > BUFFER_SIZE) {
+ fprintf(stderr, "Error: period must be between 1 and %d\n", BUFFER_SIZE);
+ usage(argv[0]);
+ return 1;
+ }
+
+ fp_t input[BUFFER_SIZE];
+ fp_t output[BUFFER_SIZE];
+ for (int_t i = 0; i < (int_t)BUFFER_SIZE; i++) {
+ output[i] = 0;
+ }
+
+ if (!ts_fill(input, BUFFER_SIZE, 0, (fp_t)BUFFER_SIZE)) {
+ fprintf(stderr, "ts_fill failed\n");
+ return 1;
+ }
+
+ fp_t * const y[1] = { output };
+ fp_t const * const x[1] = { input };
+
+ ts_constant_t c;
+ memset(&c, 0, sizeof(c));
+ *(uint_t *)c.data = period;
+ int ret = ts_average(y, 1, x, 1, BUFFER_SIZE, &c);
+ if (ret == -1) {
+ fprintf(stderr, "ts_average failed\n");
+ return 1;
+ }
+
+ /* Validate output: first valid value is at index period-1 */
+ uint_t offset = period - 1;
+ if (offset >= BUFFER_SIZE) {
+ fprintf(stderr, "error: period exceeds buffer size\n");
+ return 1;
+ }
+
+ /* Clamp display range to valid output */
+ uint_t display_end = DISPLAY_N < BUFFER_SIZE ? DISPLAY_N : BUFFER_SIZE;
+ if (offset >= display_end) {
+ fprintf(stderr, "warning: period (%" TS_PRIuint ") >= display count (%" TS_PRIuint "), showing available data\n", period, display_end);
+ display_end = BUFFER_SIZE;
+ }
+
+ for (uint_t i = offset; i < display_end; i++) {
+ printf("input[%" TS_PRIuint "] = %f SMA[%" TS_PRIuint "] = %f\n", i, input[i], i, output[i]);
+ }
+
+ return 0;
+}
diff --git a/python/pyproject.toml b/python/pyproject.toml
new file mode 100644
index 0000000..2eed6ce
--- /dev/null
+++ b/python/pyproject.toml
@@ -0,0 +1,36 @@
+[build-system]
+requires = ["setuptools>=61.0"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "ts-bindings"
+version = "0.1.0"
+description = "Python bindings for the ts (time series) C library"
+authors = [{name = "Ethan Werner"}]
+requires-python = ">=3.9"
+license = {text = "MIT"}
+classifiers = [
+ "Development Status :: 3 - Alpha",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: MIT License",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Topic :: Office/Business :: Financial",
+ "Topic :: Scientific/Engineering :: Mathematics",
+]
+dependencies = [
+ "numpy>=1.21,<3.0",
+]
+
+[project.urls]
+Homepage = "https://github.com/ethanwerner/ts"
+Repository = "https://github.com/ethanwerner/ts"
+
+[tool.setuptools.packages.find]
+include = ["ts*"]
+
+[tool.setuptools.package-data]
+ts = ["py.typed", "libts.so"]
diff --git a/python/ts/py.typed b/python/ts/py.typed
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/python/ts/py.typed
diff --git a/scripts/gen_all.py b/scripts/gen_all.py
new file mode 100644
index 0000000..dd9d395
--- /dev/null
+++ b/scripts/gen_all.py
@@ -0,0 +1,135 @@
+#!/usr/bin/env python3
+"""Generate ts_generated.h with all indicator declarations and test registry.
+
+Parses lib/*.c for ts_parameter_t declarations and emits:
+- extern ts_parameter_t const ts_<name>_p
+- uint_t ts_<name>_offset(...)
+- int ts_<name>(...)
+- bool ts_test_<name>(void) [inside #ifdef TS_IMPLEMENTATION]
+"""
+
+import importlib.util
+import os
+import re
+import sys
+
+_spec = importlib.util.spec_from_file_location(
+ "ts_parser",
+ os.path.join(os.path.dirname(os.path.abspath(__file__)), "ts_parser.py"),
+)
+if _spec and _spec.loader:
+ _ts_parser = importlib.util.module_from_spec(_spec)
+ _spec.loader.exec_module(_ts_parser)
+ parse_all = _ts_parser.parse_all
+else:
+ print("Error: could not load ts_parser", file=sys.stderr)
+ sys.exit(1)
+
+ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+TEST_DIR = os.path.join(ROOT, "test")
+OUT_H = os.path.join(ROOT, "ts_generated.h")
+OUT_TEST = os.path.join(TEST_DIR, "main_generated.c")
+
+
+def generate_header(funcs):
+ """Generate ts_generated.h."""
+ lines = []
+ lines.append("/* Auto-generated indicator declarations. Do not edit. */")
+ lines.append("")
+ lines.append("#ifndef TS_GENERATED_H")
+ lines.append("#define TS_GENERATED_H")
+ lines.append("")
+
+ for fm in funcs:
+ lines.append("extern ts_parameter_t const ts_" + fm.name + "_p;")
+ lines.append("uint_t ts_" + fm.name + "_offset( ts_constant_t const * );")
+ lines.append(
+ "int ts_"
+ + fm.name
+ + "( fp_t * const *y, uint_t y_n, fp_t const * const *x, "
+ "uint_t x_n, uint_t n, ts_constant_t const *c );"
+ )
+
+ lines.append("")
+ lines.append("#ifdef TS_IMPLEMENTATION")
+ lines.append("")
+ for fm in funcs:
+ lines.append("bool ts_test_" + fm.name + "( void );")
+ lines.append("")
+ lines.append("#endif /* TS_IMPLEMENTATION */")
+ lines.append("")
+ lines.append("#endif /* TS_GENERATED_H */")
+ lines.append("")
+
+ return "\n".join(lines)
+
+
+def generate_test_main(funcs):
+ """Generate test/main_generated.c with extern declarations and test array."""
+ lines = []
+ lines.append("/* Auto-generated test registry. Do not edit. */")
+ lines.append("")
+ lines.append("#define EXTERN_TEST(name) extern bool ts_test_##name(void)")
+ for fm in funcs:
+ lines.append("EXTERN_TEST(" + fm.name + ");")
+ lines.append("#undef EXTERN_TEST")
+ lines.append("")
+ lines.append("typedef struct {")
+ lines.append(" const char *name;")
+ lines.append(" bool (*func)(void);")
+ lines.append("} test_case_t;")
+ lines.append("")
+ lines.append("#define TEST_ENTRY(name) { #name, ts_test_##name }")
+ lines.append("")
+ lines.append("static test_case_t tests[] = {")
+ for fm in funcs:
+ lines.append(" TEST_ENTRY(" + fm.name + "),")
+ lines.append("};")
+ lines.append("")
+ lines.append("#define NUM_TESTS (sizeof(tests) / sizeof(tests[0]))")
+ lines.append("")
+ lines.append("#undef TEST_ENTRY")
+ lines.append("")
+
+ return "\n".join(lines)
+
+
+def _is_valid_c_identifier(name):
+ return bool(re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", name))
+
+
+def main():
+ funcs = parse_all(detect_x_access=False)
+
+ if not funcs:
+ print("Warning: no ts_parameter declarations found.", file=sys.stderr)
+ return 1
+
+ for fm in funcs:
+ if not _is_valid_c_identifier(fm.name):
+ print(f"Error: invalid C identifier '{fm.name}'", file=sys.stderr)
+ return 1
+
+ os.makedirs(TEST_DIR, exist_ok=True)
+
+ try:
+ with open(OUT_H, "w", encoding="utf-8") as f:
+ f.write(generate_header(funcs))
+ except OSError as e:
+ print(f"Error writing {OUT_H}: {e}", file=sys.stderr)
+ return 1
+
+ try:
+ with open(OUT_TEST, "w", encoding="utf-8") as f:
+ f.write(generate_test_main(funcs))
+ except OSError as e:
+ print(f"Error writing {OUT_TEST}: {e}", file=sys.stderr)
+ return 1
+
+ print(f"Generated {len(funcs)} declarations -> {OUT_H}")
+ print(f"Generated test registry -> {OUT_TEST}")
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/scripts/gen_bindings.py b/scripts/gen_bindings.py
new file mode 100644
index 0000000..845c8a5
--- /dev/null
+++ b/scripts/gen_bindings.py
@@ -0,0 +1,274 @@
+#!/usr/bin/env python3
+"""Generate python/ts/__init__.py from C source.
+
+Produces a compact module with a single _dispatch() function and a
+FUNC_META lookup table instead of 350+ individual wrappers.
+"""
+
+import importlib.util
+import os
+import py_compile
+import sys
+
+_spec = importlib.util.spec_from_file_location(
+ "ts_parser",
+ os.path.join(os.path.dirname(os.path.abspath(__file__)), "ts_parser.py"),
+)
+if _spec and _spec.loader:
+ _ts_parser = importlib.util.module_from_spec(_spec)
+ _spec.loader.exec_module(_ts_parser)
+ parse_all = _ts_parser.parse_all
+else:
+ print("Error: could not load ts_parser", file=sys.stderr)
+ sys.exit(1)
+
+ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+OUT = os.path.join(ROOT, "python", "ts", "__init__.py")
+
+DEFAULT_PARAMS = {
+ "ichimoku": {"tenkan": 9, "kijun": 26, "senkou": 52},
+}
+
+MIN_PARAM_DEFAULTS = {
+ "hull_ma": {"k": 2},
+ "variable_index_dynamic_average": {"period": 2},
+ "logarithm": {"b": 2.0},
+ "choppiness_index": {"k": 2},
+ "performance_ratio": {"k": 2},
+ "standard_error": {"k": 2},
+}
+
+
+def _py_default(pytype, py_fname, name):
+ if py_fname in DEFAULT_PARAMS.get(name, {}):
+ return DEFAULT_PARAMS[name][py_fname]
+ if py_fname in MIN_PARAM_DEFAULTS.get(name, {}):
+ return MIN_PARAM_DEFAULTS[name][py_fname]
+ return 1 if "uint64" in pytype else 1.0
+
+
+def main():
+ funcs = parse_all(detect_x_access=True)
+ if not funcs:
+ print("Warning: no ts_parameter declarations found.", file=sys.stderr)
+ sys.exit(1)
+
+ os.makedirs(os.path.dirname(OUT), exist_ok=True)
+
+ with open(OUT, "w", encoding="utf-8") as f:
+ # --- Header ---
+ f.write('"""Python bindings for ts (time series) C library.\n')
+ f.write("\nAuto-generated. Uses a single dispatch function backed by\n")
+ f.write('a FUNC_META lookup table.\n"""\n\n')
+
+ f.write("import ctypes\n")
+ f.write("import ctypes.util\n")
+ f.write("import os\n")
+ f.write("import numpy as np\n")
+ f.write("import threading\n\n")
+
+ f.write("_tls = threading.local()\n\n")
+
+ # --- Library loader ---
+ f.write("def _find_lib():\n")
+ f.write(' """Try system path, pkg-config, then build dir."""\n')
+ f.write(" try:\n")
+ f.write(' return ctypes.CDLL("libts.so", ctypes.RTLD_GLOBAL)\n')
+ f.write(" except OSError:\n")
+ f.write(" pass\n")
+ f.write(" try:\n")
+ f.write(" import subprocess\n")
+ f.write(" libdir = subprocess.check_output(\n")
+ f.write(' ["pkg-config", "--variable=libdir", "ts"],\n')
+ f.write(" stderr=subprocess.DEVNULL,\n")
+ f.write(" ).decode().strip()\n")
+ f.write(
+ ' return ctypes.CDLL(os.path.join(libdir, "libts.so"), ctypes.RTLD_GLOBAL)\n'
+ )
+ f.write(" except (OSError, subprocess.CalledProcessError):\n")
+ f.write(" pass\n")
+ f.write(" build_dir = os.path.join(\n")
+ f.write(
+ " os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),\n"
+ )
+ f.write(' "build",\n')
+ f.write(" )\n")
+ f.write(' lib_path = os.path.join(build_dir, "libts.so")\n')
+ f.write(" if os.path.exists(lib_path):\n")
+ f.write(" return ctypes.CDLL(lib_path, ctypes.RTLD_GLOBAL)\n")
+ f.write(
+ ' raise OSError("Cannot find libts.so. Run make install or build from source.")\n\n\n'
+ )
+
+ f.write("__version__ = '0.1.0'\n\n")
+ f.write("_lib = _find_lib()\n\n")
+
+ # --- Helpers ---
+ f.write("_CD = ctypes.c_double\n\n\n")
+
+ f.write("def _cp(arr):\n")
+ f.write(' """Copy numpy array to ctypes buffer (tracked in TLS list)."""\n')
+ f.write(" buf = (_CD * len(arr))(*arr)\n")
+ f.write(" if not hasattr(_tls, 'bufs'):\n")
+ f.write(" _tls.bufs = []\n")
+ f.write(" _tls.bufs.append(buf)\n")
+ f.write(" return buf\n\n\n")
+
+ f.write("def _clr():\n")
+ f.write(' """Clear tracked ctypes buffers."""\n')
+ f.write(" try:\n")
+ f.write(" if hasattr(_tls, 'bufs'):\n")
+ f.write(" _tls.bufs.clear()\n")
+ f.write(" except Exception:\n")
+ f.write(" pass\n\n\n")
+
+ # --- Parameter structs ---
+ f.write("# --- Parameter structs ---\n")
+ seen_structs = set()
+ for fm in sorted(funcs, key=lambda x: x.name):
+ if fm.struct_fields and fm.name not in seen_structs:
+ cls_name = f"ts_{fm.name}_params_t"
+ f.write(f"class {cls_name}(ctypes.Structure):\n")
+ f.write(" _fields_ = [\n")
+ for c_fname, py_fname, pytype in fm.struct_fields:
+ f.write(f' ("{py_fname}", {pytype}),\n')
+ f.write(" ]\n\n")
+ seen_structs.add(fm.name)
+
+ # --- FUNC_META table ---
+ f.write("# --- Function metadata table ---\n")
+ f.write(
+ "# Each entry: (name, x_n, y_n, struct_class_or_None, [(param_name, default), ...])\n"
+ )
+ f.write("FUNC_META = [\n")
+ for fm in sorted(funcs, key=lambda x: x.name):
+ struct_cls = None
+ params = []
+ if fm.struct_fields:
+ struct_cls = f"ts_{fm.name}_params_t"
+ for c_fname, py_fname, pytype in fm.struct_fields:
+ d = _py_default(pytype, py_fname, fm.name)
+ params.append(f" ('{py_fname}', {d!r}),")
+ f.write(
+ f" ({fm.name!r}, {fm.x_n}, {fm.y_n}, {struct_cls}, [{chr(10).join(params)}]),\n"
+ )
+ f.write("]\n\n")
+
+ # --- Single dispatch function ---
+ f.write("# --- Dispatch ---\n")
+ f.write("def _dispatch(meta, *x_arrays):\n")
+ f.write(' """Call a ts indicator via ctypes, given a FUNC_META entry."""\n')
+ f.write(" name, x_n, y_n, struct_cls, param_defaults = meta\n")
+ f.write(" if len(x_arrays) != x_n:\n")
+ f.write(
+ ' raise TypeError(f"Expected {x_n} input arrays, got {len(x_arrays)}")\n'
+ )
+ f.write(" if x_n > 0:\n")
+ f.write(" n = x_arrays[0].shape[0]\n")
+ f.write(" for a in x_arrays:\n")
+ f.write(" if a.shape[0] != n:\n")
+ f.write(
+ ' raise ValueError(f"Array length mismatch: expected {n}, got {a.shape[0]}")\n'
+ )
+ f.write(" else:\n")
+ f.write(" n = 0\n")
+ f.write(" y = [np.zeros(n, dtype=np.float64) for _ in range(y_n)]\n")
+ f.write(" try:\n")
+ f.write(" x_bufs = [_cp(x_arrays[i]) for i in range(x_n)]\n")
+ f.write(
+ " y_ptrs = [y[i].ctypes.data_as(ctypes.POINTER(ctypes.c_double)) for i in range(y_n)]\n"
+ )
+ f.write(" c_func = getattr(_lib, f'ts_{name}_flat')\n")
+ f.write(" if struct_cls is not None:\n")
+ f.write(" _p = struct_cls()\n")
+ f.write(" for k, v in param_defaults:\n")
+ f.write(" setattr(_p, k, v)\n")
+ f.write(
+ " ret = c_func(*(y_ptrs + x_bufs + [ctypes.c_uint64(n), ctypes.byref(_p)]))\n"
+ )
+ f.write(" else:\n")
+ f.write(
+ " ret = c_func(*(y_ptrs + x_bufs + [ctypes.c_uint64(n), None]))\n"
+ )
+ f.write(" if ret != 0:\n")
+ f.write(
+ ' raise RuntimeError(f"ts_{name} failed with return code {ret}")\n'
+ )
+ f.write(" return y[0] if y_n == 1 else tuple(y)\n")
+ f.write(" finally:\n")
+ f.write(" _clr()\n\n\n")
+
+ # --- Helper for param override ---
+ f.write(
+ "def _dispatch_with_params(name, x_n, y_n, struct_cls, _p, x_arrays):\n"
+ )
+ f.write(' """Call indicator with a pre-built params struct."""\n')
+ f.write(" if len(x_arrays) != x_n:\n")
+ f.write(
+ ' raise TypeError(f"Expected {x_n} input arrays, got {len(x_arrays)}")\n'
+ )
+ f.write(" n = x_arrays[0].shape[0] if x_n > 0 else 0\n")
+ f.write(" y = [np.zeros(n, dtype=np.float64) for _ in range(y_n)]\n")
+ f.write(" try:\n")
+ f.write(" x_bufs = [_cp(x_arrays[i]) for i in range(x_n)]\n")
+ f.write(
+ " y_ptrs = [y[i].ctypes.data_as(ctypes.POINTER(ctypes.c_double)) for i in range(y_n)]\n"
+ )
+ f.write(" c_func = getattr(_lib, f'ts_{name}_flat')\n")
+ f.write(
+ " ret = c_func(*(y_ptrs + x_bufs + [ctypes.c_uint64(n), ctypes.byref(_p)]))\n"
+ )
+ f.write(" if ret != 0:\n")
+ f.write(
+ ' raise RuntimeError(f"ts_{name} failed with return code {ret}")\n'
+ )
+ f.write(" return y[0] if y_n == 1 else tuple(y)\n")
+ f.write(" finally:\n")
+ f.write(" _clr()\n\n\n")
+
+ # --- Dynamic attribute access ---
+ f.write("# --- Dynamic attribute access ---\n")
+ f.write("_FUNC_MAP = {m[0]: m for m in FUNC_META}\n\n\n")
+
+ f.write("def __getattr__(name):\n")
+ f.write(' """Return a callable wrapper for indicator `name`."""\n')
+ f.write(" if name.startswith('_'):\n")
+ f.write(
+ ' raise AttributeError(f"module {__name__!r} has no attribute {name!r}")\n'
+ )
+ f.write(" meta = _FUNC_MAP.get(name)\n")
+ f.write(" if meta is None:\n")
+ f.write(
+ ' raise AttributeError(f"{name!r} is not a known ts indicator")\n'
+ )
+ f.write(" name_s, x_n, y_n, struct_cls, param_defaults = meta\n")
+ f.write(" def wrapper(*x_arrays, **kw):\n")
+ f.write(" for a in x_arrays:\n")
+ f.write(" if not isinstance(a, np.ndarray):\n")
+ f.write(
+ ' raise TypeError(f"Expected numpy.ndarray, got {type(a).__name__}")\n'
+ )
+ f.write(" if struct_cls is not None and kw:\n")
+ f.write(" _p = struct_cls()\n")
+ f.write(" for k, v in kw.items():\n")
+ f.write(" if hasattr(_p, k):\n")
+ f.write(" setattr(_p, k, v)\n")
+ f.write(" else:\n")
+ f.write(
+ ' raise TypeError(f"Unknown parameter {k!r} for {name_s}")\n'
+ )
+ f.write(
+ " return _dispatch_with_params(name_s, x_n, y_n, struct_cls, _p, x_arrays)\n"
+ )
+ f.write(" return _dispatch(meta, *x_arrays)\n")
+ f.write(" return wrapper\n\n\n")
+
+ # --- __all__ ---
+ f.write("__all__ = [m[0] for m in FUNC_META]\n")
+
+ print(f"Generated {len(funcs)} entries -> {OUT}")
+ py_compile.compile(OUT, doraise=True)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/gen_flat.py b/scripts/gen_flat.py
new file mode 100644
index 0000000..c659432
--- /dev/null
+++ b/scripts/gen_flat.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python3
+"""Generate flat-array C wrappers for all ts functions.
+
+Each wrapper takes flat input/output arrays instead of pointer-of-pointers,
+eliminating ctypes pointer marshaling overhead.
+"""
+
+import argparse
+import importlib.util
+import os
+import sys
+
+_spec = importlib.util.spec_from_file_location(
+ "ts_parser",
+ os.path.join(os.path.dirname(os.path.abspath(__file__)), "ts_parser.py"),
+)
+if _spec and _spec.loader:
+ _ts_parser = importlib.util.module_from_spec(_spec)
+ _spec.loader.exec_module(_ts_parser)
+ parse_all = _ts_parser.parse_all
+else:
+ print("Error: could not load ts_parser", file=sys.stderr)
+ sys.exit(1)
+
+ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+OUT = os.path.join(ROOT, "lib", "ts_flat_generated.c")
+
+
+def generate_wrappers(funcs, dry_run=False):
+ """Generate flat-array C wrapper functions."""
+ if dry_run:
+ print(f"[dry-run] Would generate {len(funcs)} flat wrappers -> {OUT}")
+ return
+
+ with open(OUT, "w", encoding="utf-8") as f:
+ f.write("/* Auto-generated flat-array wrappers. Do not edit. */\n\n")
+ f.write("#include <string.h>\n")
+ f.write('#include "ts.h"\n\n')
+
+ for fm in funcs:
+ safe_x_n = max(fm.x_n, 1)
+ safe_y_n = max(fm.y_n, 1)
+
+ f.write(f"/* {fm.name}: x_n={fm.x_n}, y_n={fm.y_n}, c_n={fm.c_n} */\n")
+ f.write(f"int ts_{fm.name}_flat(\n")
+
+ for i in range(fm.y_n):
+ f.write(f" fp_t *y{i},\n")
+
+ for i in range(fm.x_n):
+ f.write(f" fp_t const *x{i},\n")
+
+ f.write(f" uint_t n,\n")
+ f.write(f" ts_constant_t const *params\n")
+ f.write(f")\n{{\n")
+
+ f.write(f" fp_t * const _y[{safe_y_n}] = {{")
+ f.write(", ".join(f"y{i}" for i in range(fm.y_n)))
+ f.write("};\n")
+
+ f.write(f" fp_t const * const _x[{safe_x_n}] = {{")
+ f.write(", ".join(f"x{i}" for i in range(fm.x_n)))
+ f.write("};\n")
+
+ for i in range(fm.y_n):
+ f.write(f" memset(y{i}, 0, n * sizeof(fp_t));\n")
+
+ f.write(
+ f" return ts_{fm.name}(_y, {fm.y_n}, _x, {fm.x_n}, n, params);\n"
+ )
+ f.write("}\n\n")
+
+
+def main():
+ parser = argparse.ArgumentParser(description="Generate flat-array C wrappers")
+ parser.add_argument(
+ "--dry-run",
+ action="store_true",
+ help="Print what would be generated without writing",
+ )
+ args = parser.parse_args()
+
+ funcs = parse_all(detect_x_access=True)
+
+ if not funcs:
+ print("Warning: no ts_parameter declarations found.", file=sys.stderr)
+ sys.exit(1)
+
+ generate_wrappers(funcs, dry_run=args.dry_run)
+
+ if not args.dry_run:
+ print(f"Generated {len(funcs)} flat wrappers -> {OUT}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/gen_fuzz.py b/scripts/gen_fuzz.py
new file mode 100644
index 0000000..a1d20e3
--- /dev/null
+++ b/scripts/gen_fuzz.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python3
+"""Generate fuzz table entries from ts indicator metadata.
+
+Parses lib/*.c via ts_parser to extract function names and input/output
+counts, then generates a C source file with the complete fuzz table.
+"""
+
+import argparse
+import importlib.util
+import os
+import sys
+
+_spec = importlib.util.spec_from_file_location(
+ "ts_parser",
+ os.path.join(os.path.dirname(os.path.abspath(__file__)), "ts_parser.py"),
+)
+if _spec and _spec.loader:
+ _ts_parser = importlib.util.module_from_spec(_spec)
+ _spec.loader.exec_module(_ts_parser)
+ parse_all = _ts_parser.parse_all
+else:
+ print("Error: could not load ts_parser", file=sys.stderr)
+ sys.exit(1)
+
+OUT = os.path.join(
+ os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
+ "test",
+ "fuzz_table_generated.c",
+)
+
+
+def generate_table(funcs, dry_run=False):
+ """Generate the fuzz table C source file."""
+ if dry_run:
+ print(f"[dry-run] Would generate {len(funcs)} fuzz entries -> {OUT}")
+ return
+
+ with open(OUT, "w", encoding="utf-8") as f:
+ f.write("/* Auto-generated fuzz table. Do not edit. */\n\n")
+ f.write('#include "ts.h"\n\n')
+ f.write("static const fuzz_entry_t _fuzz_table_generated[] = {\n")
+
+ for fm in funcs:
+ f.write(f" {{ts_{fm.name}, {fm.x_n}, {fm.y_n}}},\n")
+
+ f.write("};\n\n")
+ f.write(
+ "static const uint_t _fuzz_generated_n = sizeof(_fuzz_table_generated) / sizeof(_fuzz_table_generated[0]);\n"
+ )
+
+
+def main():
+ parser = argparse.ArgumentParser(description="Generate fuzz table from registry")
+ parser.add_argument(
+ "--dry-run",
+ action="store_true",
+ help="Print what would be generated without writing",
+ )
+ args = parser.parse_args()
+
+ funcs = parse_all(detect_x_access=True)
+
+ if not funcs:
+ print("Warning: no ts_parameter declarations found.", file=sys.stderr)
+ sys.exit(1)
+
+ generate_table(funcs, dry_run=args.dry_run)
+
+ if not args.dry_run:
+ print(f"Generated {len(funcs)} fuzz entries -> {OUT}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/gen_registry.py b/scripts/gen_registry.py
new file mode 100644
index 0000000..e80759e
--- /dev/null
+++ b/scripts/gen_registry.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+"""Generate ts_registry_generated.c with registry array and getter functions."""
+
+import importlib.util
+import os
+import re
+import sys
+
+_spec = importlib.util.spec_from_file_location(
+ "ts_parser",
+ os.path.join(os.path.dirname(os.path.abspath(__file__)), "ts_parser.py"),
+)
+if _spec and _spec.loader:
+ ts_parser = importlib.util.module_from_spec(_spec)
+ _spec.loader.exec_module(ts_parser)
+ parse_all = ts_parser.parse_all
+else:
+ print("Error: could not load ts_parser", file=sys.stderr)
+ sys.exit(1)
+
+ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+OUT = os.path.join(ROOT, "lib", "ts_registry_generated.c")
+
+
+def _resolve_params_size(params_size, name):
+ """Resolve params_size to a compile-time constant.
+
+ If params_size is '0', keep it. If it's sizeof(ts_X_params_t), check
+ if the struct is visible in ts.h. If not, compute size from definition.
+ """
+ if params_size == "0":
+ return "0"
+
+ header_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "ts.h")
+ try:
+ with open(header_path, encoding="utf-8") as hf:
+ hcontent = hf.read()
+ if re.search(r"ts_" + re.escape(name) + r"_params_t", hcontent):
+ return params_size
+ except FileNotFoundError:
+ pass
+ # Struct not in header, compute size from definition
+ import glob
+
+ lib_dir = os.path.join(ROOT, "lib")
+ for cfile in glob.glob(os.path.join(lib_dir, "*.c")):
+ try:
+ with open(cfile, encoding="utf-8") as f:
+ content = f.read()
+ except OSError:
+ continue
+ struct_m = re.search(
+ r"typedef\s+struct\s*\{([^}]+)\}\s+ts_" + re.escape(name) + r"_params_t",
+ content,
+ )
+ if struct_m:
+ body = struct_m.group(1)
+ n_fp = len(re.findall(r"fp_t\s+", body))
+ n_uint = len(re.findall(r"uint_t\s+", body))
+ return str(n_fp * 8 + n_uint * 8)
+ return "0"
+
+
+def main():
+ funcs = parse_all(detect_x_access=False)
+
+ if not funcs:
+ print("Warning: no ts_parameter declarations found.", file=sys.stderr)
+ sys.exit(1)
+
+ with open(OUT, "w", encoding="utf-8") as f:
+ f.write("/* Auto-generated function registry. Do not edit. */\n\n")
+ f.write('#include "ts.h"\n\n')
+ f.write(f"static const uint_t _ts_fn_count = {len(funcs)};\n\n")
+ f.write("static const ts_fn_info_t _ts_registry[] = {\n")
+ for fm in funcs:
+ ps = _resolve_params_size(fm.params_size, fm.name)
+ f.write(
+ f" {{ .n_inputs = {fm.x_n}, .n_outputs = {fm.y_n}, "
+ f".n_constants = {fm.c_n}, .params_size = {ps} }},\n"
+ )
+ f.write("};\n\n")
+ f.write("static const char * const _ts_fn_names[] = {\n")
+ for fm in funcs:
+ f.write(f' "{fm.name}",\n')
+ f.write("};\n\n")
+
+ # Getter functions
+ f.write("uint_t ts_fn_count( void ) { return _ts_fn_count; }\n\n")
+ f.write("const ts_fn_info_t* ts_registry_get( uint_t i )\n")
+ f.write("{\n")
+ f.write(" return i < _ts_fn_count ? &_ts_registry[i] : NULL;\n")
+ f.write("}\n\n")
+ f.write("const char* ts_fn_name_get( uint_t i )\n")
+ f.write("{\n")
+ f.write(" return i < _ts_fn_count ? _ts_fn_names[i] : NULL;\n")
+ f.write("}\n")
+
+ print(f"Generated {len(funcs)} entries -> {OUT}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/ts_parser.py b/scripts/ts_parser.py
new file mode 100644
index 0000000..2ad3629
--- /dev/null
+++ b/scripts/ts_parser.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python3
+"""Shared parser for ts indicator metadata.
+
+Parses lib/*.c for ts_parameter_t declarations and extracts:
+- name, n_inputs, n_outputs, n_constants
+- Actual x[N] access count (may exceed declared n_inputs)
+- Parameter struct fields
+
+All generators should import this instead of duplicating regex logic.
+"""
+
+import glob
+import os
+import re
+import warnings
+
+LIB_DIR = os.path.join(
+ os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "lib"
+)
+
+SKIP_PREFIXES = ("ts_flat", "ts_registry", "gen_", "update_")
+SKIP_NAMES = {"test"}
+
+PY_RESERVED_NAMES = {"lambda": "lambda_", "type": "type_", "id": "id_"}
+
+VOLUME_INDEX = 4
+OHLCV_COUNT = 5
+
+
+class FuncMeta:
+ """Metadata for a single ts indicator function."""
+
+ __slots__ = ("name", "x_n", "y_n", "c_n", "params_size", "struct_fields")
+
+ def __init__(self, name, x_n, y_n, c_n, params_size="0", struct_fields=None):
+ self.name = name
+ self.x_n = x_n
+ self.y_n = y_n
+ self.c_n = c_n
+ self.params_size = params_size
+ self.struct_fields = struct_fields or []
+
+
+def _detect_actual_x_n(content, match_end, name):
+ """Detect actual x[N] access in function body."""
+ remaining = content[match_end:]
+ func_def = re.search(rf"\bts_{name}\s*\([^)]*\)\s*{{", remaining)
+ if not func_def:
+ return 0
+ brace_pos = remaining.find("{", func_def.start())
+ depth = 0
+ func_body = ""
+ for i in range(brace_pos, len(remaining)):
+ if remaining[i] == "{":
+ depth += 1
+ elif remaining[i] == "}":
+ depth -= 1
+ if depth == 0:
+ func_body = remaining[brace_pos : i + 1]
+ break
+ indices = [
+ int(xm.group(1)) for xm in re.finditer(r"x\s*\[\s*(\d+)\s*\]", func_body)
+ ]
+ return max(indices) + 1 if indices else 0
+
+
+def _parse_struct_fields(content, name):
+ """Parse parameter struct fields for a function."""
+ struct_m = re.search(
+ r"typedef\s+struct\s*\{([^}]+)\}\s+ts_" + re.escape(name) + r"_params_t",
+ content,
+ )
+ if not struct_m:
+ return []
+ fields_text = struct_m.group(1)
+ fields_text = re.sub(r"/\*.*?\*/", "", fields_text, flags=re.DOTALL)
+ fields_text = re.sub(r"//.*", "", fields_text)
+ fields = []
+ for fm in re.finditer(r"^\s*(\w+)\s+(\w+)\s*;", fields_text, re.MULTILINE):
+ ctype, c_fname = fm.group(1), fm.group(2)
+ if ctype == "uint_t":
+ pytype = "ctypes.c_uint64"
+ elif ctype == "fp_t":
+ pytype = "ctypes.c_double"
+ else:
+ warnings.warn(
+ f"Unknown type '{ctype}' in struct ts_{name}_params_t, defaulting to c_double"
+ )
+ pytype = "ctypes.c_double"
+ py_fname = PY_RESERVED_NAMES.get(c_fname, c_fname)
+ fields.append((c_fname, py_fname, pytype))
+ return fields
+
+
+def parse_all(detect_x_access=True):
+ """Parse all ts indicator functions from lib/*.c.
+
+ Args:
+ detect_x_access: If True, detect actual x[N] access in function body
+ and adjust x_n accordingly.
+
+ Returns:
+ List of FuncMeta objects.
+ """
+ funcs = []
+ for cfile in sorted(glob.glob(os.path.join(LIB_DIR, "*.c"))):
+ basename = os.path.basename(cfile)
+ stem = basename.replace(".c", "")
+ if any(stem.startswith(p) for p in SKIP_PREFIXES) or stem in SKIP_NAMES:
+ continue
+ try:
+ with open(cfile, encoding="utf-8") as f:
+ content = f.read()
+ except OSError as e:
+ warnings.warn(f"Could not read {cfile}: {e}")
+ continue
+
+ for m in re.finditer(
+ r"ts_parameter_t\s+const\s+ts_(\w+)_p\s*=\s*\{\s*"
+ r"\.n_inputs\s*=\s*(\d+)\s*,\s*"
+ r"\.n_outputs\s*=\s*(\d+)\s*,\s*"
+ r"\.n_constants\s*=\s*(\d+)"
+ r"(?:\s*,\s*\.params_size\s*=\s*(\S+?))?\s*\}",
+ content,
+ ):
+ name = m.group(1)
+ x_n = int(m.group(2))
+ y_n = int(m.group(3))
+ c_n = int(m.group(4))
+ params_size = m.group(5) or "0"
+
+ if detect_x_access:
+ actual = _detect_actual_x_n(content, m.end(), name)
+ if actual > x_n:
+ x_n = actual
+ if x_n < OHLCV_COUNT and re.search(
+ r"\bx\s*\[\s*" + str(VOLUME_INDEX) + r"\s*\]",
+ content[m.end() :],
+ ):
+ x_n = OHLCV_COUNT
+
+ struct_fields = _parse_struct_fields(content, name) if c_n > 0 else []
+
+ funcs.append(FuncMeta(name, x_n, y_n, c_n, params_size, struct_fields))
+
+ return funcs
+
+
+def parse_names_only():
+ """Parse function names and basic metadata (no x access detection).
+
+ Returns:
+ Dict mapping name -> {"x_n": int, "y_n": int}.
+ """
+ meta = {}
+ for fm in parse_all(detect_x_access=True):
+ if fm.name not in meta:
+ meta[fm.name] = {"x_n": fm.x_n, "y_n": fm.y_n}
+ return meta
diff --git a/test/absolute_value.c b/test/absolute_value.c
new file mode 100644
index 0000000..2180501
--- /dev/null
+++ b/test/absolute_value.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_absolute_value( void )
+{
+ fp_t const x0[] = { -4, -3, -2, -1, 0, 1, 2, 3, 4 };
+ fp_t const e0[] = { 4, 3, 2, 1, 0, 1, 2, 3, 4 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_absolute_value, ts_absolute_value_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_absolute_value_p);
+}
diff --git a/test/acceleration_bands.c b/test/acceleration_bands.c
new file mode 100644
index 0000000..5911e8b
--- /dev/null
+++ b/test/acceleration_bands.c
@@ -0,0 +1,28 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_acceleration_bands( void )
+{
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
+ fp_t const cp[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+
+ fp_t const * const x[] = { o, h, l, cp };
+
+ /* k=3, offset=2, y_n=8 */
+ /* Rolling HH3: 12,13,14,15,16,17,18,19,20,21 */
+ /* Rolling LL3: 9,9,9,10,11,12,13,14,15,16 */
+ /* TP = (H+L+C)/3: 10.667,11.667,12.667,13.667,14.667,15.667,16.667,17.667,18.667,19.667 */
+ /* SMA3(HH3): 13,14,15,16,17,18,19,20 */
+ /* SMA3(TP): 11.667,12.667,13.667,14.667,15.667,16.667,17.667,18.667 */
+ /* SMA3(LL3): 9,9.333,10,11,12,13,14,15 */
+ fp_t const eu[] = { 13, 14, 15, 16, 17, 18, 19, 20 };
+ fp_t const em[] = { 11.66667, 12.66667, 13.66667, 14.66667, 15.66667, 16.66667, 17.66667, 18.66667 };
+ fp_t const el[] = { 9, 9.33333, 10, 11, 12, 13, 14, 15 };
+ fp_t const * const exp[3] = { eu, em, el };
+
+ uint_t k = 3;
+ return ts_test(ts_acceleration_bands, ts_acceleration_bands_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_acceleration_bands_p);
+}
diff --git a/test/acosh.c b/test/acosh.c
new file mode 100644
index 0000000..c9ecfd1
--- /dev/null
+++ b/test/acosh.c
@@ -0,0 +1,27 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_acosh( void )
+{
+ fp_t const x0[] = { 1.000000, 1.500000, 2.000000, 3.000000, 5.000000 };
+ fp_t const e0[] = { 0.000000, 0.962424, 1.316958, 1.762747, 2.292432 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ bool result = ts_test(ts_acosh, ts_acosh_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_acosh_p);
+
+ fp_t const xb[] = { 0.000000, -1.000000, 0.500000 };
+
+ for( uint_t i = 0; i < sizeof( xb ) / sizeof( *xb ); ++i )
+ {
+ fp_t tmp = 0;
+ fp_t const * const xi[1] = { &xb[i] };
+ fp_t * const yi[1] = { &tmp };
+ ts_acosh(yi, 1, xi, 1, 1, NULL);
+ result &= isnan( tmp );
+ }
+
+ return result;
+}
diff --git a/test/ad.c b/test/ad.c
new file mode 100644
index 0000000..677068f
--- /dev/null
+++ b/test/ad.c
@@ -0,0 +1,18 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_ad( void )
+{
+ fp_t const o[] = { 10, 11, 12, 11, 12 };
+ fp_t const h[] = { 12, 13, 14, 12, 14 };
+ fp_t const l[] = { 8, 9, 10, 9, 10 };
+ fp_t const c[] = { 11, 12, 10, 11, 11 };
+ fp_t const v[] = { 100, 200, 300, 400, 500 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t const e[] = { 50, 150, -150, -16.666666666666668, -266.66666666666669 };
+ fp_t const * const e_arr[1] = { e };
+
+ return ts_test(ts_ad, ts_ad_offset, x, e_arr, 5, (ts_constant_t *)NULL, &ts_ad_p);
+}
diff --git a/test/ad_oscillator.c b/test/ad_oscillator.c
new file mode 100644
index 0000000..623fc44
--- /dev/null
+++ b/test/ad_oscillator.c
@@ -0,0 +1,46 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_ad_oscillator( void )
+{
+ fp_t const o[] = { 10, 11, 12, 11, 12 };
+ fp_t const h[] = { 12, 13, 14, 12, 14 };
+ fp_t const l[] = { 8, 9, 10, 9, 10 };
+ fp_t const c[] = { 11, 12, 10, 11, 11 };
+ fp_t const v[] = { 100, 200, 300, 400, 500 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t fast = 3;
+ fp_t slow = 5;
+ fp_t a_fast = 1.0 / (fp_t)fast;
+ fp_t a_slow = 1.0 / (fp_t)slow;
+
+ fp_t ad = 0;
+ fp_t ema_f = 0;
+ fp_t ema_s = 0;
+
+ fp_t hl0 = h[0] - l[0];
+ fp_t mf0 = hl0 != 0 ? ( (c[0] - l[0]) - (h[0] - c[0]) ) / hl0 * v[0] : 0;
+ ad = mf0;
+ ema_f = ad;
+ ema_s = ad;
+
+ fp_t e[5] = { 0 };
+
+ for( uint_t i = 1; i < 5; ++i )
+ {
+ fp_t hl = h[i] - l[i];
+ fp_t mf = hl != 0 ? ( (c[i] - l[i]) - (h[i] - c[i]) ) / hl * v[i] : 0;
+ ad += mf;
+ ema_f = ema_f + a_fast * ( ad - ema_f );
+ ema_s = ema_s + a_slow * ( ad - ema_s );
+ e[i] = ema_f - ema_s;
+ }
+
+ fp_t const * const e_arr[1] = { e };
+
+ fp_t constants[2] = { fast, slow };
+
+ return ts_test(ts_ad_oscillator, ts_ad_oscillator_offset, x, e_arr, 5, (ts_constant_t *)constants, &ts_ad_oscillator_p);
+}
diff --git a/test/addition.c b/test/addition.c
new file mode 100644
index 0000000..a913da7
--- /dev/null
+++ b/test/addition.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_addition( void )
+{
+ fp_t x0[] = { -4, -3, -2, -1, 0, 1, 2, 3, 4 };
+ fp_t x1[] = { -4, -3, -2, -1, 0, 1, 2, 3, 4 };
+ fp_t e0[] = { -8, -6, -4, -2, 0, 2, 4, 6, 8 };
+
+ fp_t const * const x[] = { x0, x1 };
+ fp_t const * const e[] = { e0 };
+
+ return ts_test(ts_addition, ts_addition_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_addition_p);
+}
diff --git a/test/adx.c b/test/adx.c
new file mode 100644
index 0000000..e8df67e
--- /dev/null
+++ b/test/adx.c
@@ -0,0 +1,61 @@
+#include <math.h>
+#include <stdlib.h>
+#include "ts.h"
+
+bool ts_test_adx( void )
+{
+ uint_t n = 14;
+ uint_t k = 3;
+
+ /* Monotonically rising data: +DM=1 each bar, -DM=0, TR=3 */
+ fp_t *x[4];
+ x[0] = malloc( n * sizeof( fp_t ) );
+ x[1] = malloc( n * sizeof( fp_t ) );
+ x[2] = malloc( n * sizeof( fp_t ) );
+ x[3] = malloc( n * sizeof( fp_t ) );
+
+ for ( uint_t i = 0; i < n; ++i )
+ {
+ x[0][i] = 10 + i;
+ x[1][i] = 12 + i;
+ x[2][i] = 9 + i;
+ x[3][i] = 11 + i;
+ }
+
+ /* Expected: +DI=100/3, -DI=0, DX=100, ADX=100 (converged EMAs) */
+ fp_t edp[] = { 100.0 / 3, 100.0 / 3, 100.0 / 3, 100.0 / 3, 100.0 / 3, 100.0 / 3, 100.0 / 3, 100.0 / 3, 100.0 / 3, 100.0 / 3 };
+ fp_t edm[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+ fp_t edx[] = { 100, 100, 100, 100, 100, 100, 100, 100, 100, 100 };
+ fp_t ead[] = { 100, 100, 100, 100, 100, 100, 100, 100, 100, 100 };
+ fp_t *e[4] = { edp, edm, edx, ead };
+
+ uint_t offset = k + 1;
+ uint_t y_n = n - offset;
+
+ /* Allocate full-size output since the function writes to absolute
+ * indices [offset..n-1], not compacted [0..y_n-1] */
+ fp_t *y[4];
+ for ( uint_t i = 0; i < 4; ++i )
+ y[i] = calloc( n, sizeof( fp_t ) );
+
+ uint8_t c[TS_CONSTANT_SIZE] = { 0 };
+ *( uint_t * )c = k;
+
+ ts_adx(y, ts_adx_p.n_outputs, (fp_t const * const *)x, ts_adx_p.n_inputs, n, (ts_constant_t const *)c);
+
+ bool result = true;
+ for ( uint_t i = 0; i < 4; ++i )
+ {
+ for ( uint_t j = 0; j < y_n; ++j )
+ {
+ if ( fabs( y[i][offset + j] - e[i][j] ) > TS_TEST_EPSILON )
+ result = false;
+ }
+ }
+
+ for ( uint_t i = 0; i < 4; ++i )
+ free( y[i] );
+ free( x[0] ); free( x[1] ); free( x[2] ); free( x[3] );
+
+ return result;
+}
diff --git a/test/alligator.c b/test/alligator.c
new file mode 100644
index 0000000..0288d1a
--- /dev/null
+++ b/test/alligator.c
@@ -0,0 +1,21 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_alligator( void )
+{
+ fp_t const x[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 };
+
+ fp_t const * const xp[1] = { x };
+
+ /* offset=12, y_n=12, comparison indices 12..23 */
+ /* Jaw (shift 8) writes at indices 8..19 → overlap 12..19 (8 values) */
+ /* Teeth (shift 5) writes at indices 5..21 → overlap 12..21 (10 values) */
+ /* Lips (shift 3) writes at indices 3..22 → overlap 12..22 (11 values) */
+ fp_t const ej[] = { 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 0, 0, 0, 0 };
+ fp_t const et[] = { 11.5, 12.5, 13.5, 14.5, 15.5, 16.5, 17.5, 18.5, 19.5, 20.5, 0, 0 };
+ fp_t const el[] = { 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 0 };
+ fp_t const * const exp[3] = { ej, et, el };
+
+ return ts_test(ts_alligator, ts_alligator_offset, xp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)NULL, &ts_alligator_p);
+}
diff --git a/test/angle_deg_to_rad.c b/test/angle_deg_to_rad.c
new file mode 100644
index 0000000..2e73988
--- /dev/null
+++ b/test/angle_deg_to_rad.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_angle_deg_to_rad( void )
+{
+ fp_t const x0[] = { 0.000000, 45.000000, 90.000000, 180.000000, 270.000000, 360.000000 };
+ fp_t const e0[] = { 0.000000, 0.785398, 1.570796, 3.141593, 4.712389, 6.283185 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_angle_deg_to_rad, ts_angle_deg_to_rad_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_angle_deg_to_rad_p);
+}
diff --git a/test/angle_normalize.c b/test/angle_normalize.c
new file mode 100644
index 0000000..bdfed2e
--- /dev/null
+++ b/test/angle_normalize.c
@@ -0,0 +1,11 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_angle_normalize( void )
+{
+ fp_t const x0[] = { 0.0, PI, -PI, 2*PI, -2*PI, 3*PI, -3*PI };
+ fp_t const e0[] = { 0.0, PI, -PI, 0.0, 0.0, PI, -PI };
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+ return ts_test(ts_angle_normalize, ts_angle_normalize_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_angle_normalize_p);
+}
diff --git a/test/angle_rad_to_deg.c b/test/angle_rad_to_deg.c
new file mode 100644
index 0000000..899895e
--- /dev/null
+++ b/test/angle_rad_to_deg.c
@@ -0,0 +1,11 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_angle_rad_to_deg( void )
+{
+ fp_t const x0[] = { 0.0, PI/4, PI/3, PI/2, PI, 2*PI };
+ fp_t const e0[] = { 0.0, 45.0, 60.0, 90.0, 180.0, 360.0 };
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+ return ts_test(ts_angle_rad_to_deg, ts_angle_rad_to_deg_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_angle_rad_to_deg_p);
+}
diff --git a/test/annualized_historical_volatility.c b/test/annualized_historical_volatility.c
new file mode 100644
index 0000000..69a881e
--- /dev/null
+++ b/test/annualized_historical_volatility.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_annualized_historical_volatility( void )
+{
+ fp_t const x0[] = { 100, 101, 100, 102, 101, 100, 103, 101, 102, 100 };
+ fp_t const * const x[] = { x0 };
+
+ fp_t const e0[] = { 0.196453, 0.222285, 0.222285, 0.295293, 0.337649, 0.320724, 0.221194, 0 };
+
+ fp_t const * const e[1] = { e0 };
+
+ uint_t k = 3;
+ return ts_test(ts_annualized_historical_volatility, ts_annualized_historical_volatility_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&k, &ts_annualized_historical_volatility_p);
+}
diff --git a/test/apo.c b/test/apo.c
new file mode 100644
index 0000000..7af9f5e
--- /dev/null
+++ b/test/apo.c
@@ -0,0 +1,41 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_apo( void )
+{
+ fp_t const x[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+
+ fp_t const * const inp[1] = { x };
+
+ /* fast=3, slow=5: fast_a=1/3, slow_a=1/5 */
+ /* fast EMA: 10, 10.333, 10.889, 11.593, 12.395, 13.263, 14.176, 15.117, 16.078, 17.052 */
+ /* slow EMA: 10, 10.2, 10.44, 10.752, 11.131, 11.575, 12.085, 12.666, 13.326, 14.062 */
+ /* APO: 0.1333, 0.4481, 0.8405, 1.2634, 1.6876, 2.0930, 2.4545, 2.7521, 2.9901 */
+
+ fp_t fast_e = 10, slow_e = 10;
+ fp_t fast_a = 1.0 / 3, slow_a = 1.0 / 5;
+ fp_t e[6] = { 0 };
+
+ uint_t offset = 4;
+ for( uint_t i = 1; i <= offset; ++i )
+ {
+ fast_e = fast_e + fast_a * ( x[i] - fast_e );
+ slow_e = slow_e + slow_a * ( x[i] - slow_e );
+ }
+ e[0] = fast_e - slow_e;
+
+ for( uint_t i = offset + 1; i < 10; ++i )
+ {
+ fast_e = fast_e + fast_a * ( x[i] - fast_e );
+ slow_e = slow_e + slow_a * ( x[i] - slow_e );
+ e[i - offset] = fast_e - slow_e;
+ }
+
+ fp_t const * const exp[1] = { e };
+
+ typedef struct { fp_t fast; fp_t slow; } c_t;
+ c_t c = { .fast = 3, .slow = 5 };
+
+ return ts_test(ts_apo, ts_apo_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&c, &ts_apo_p);
+}
diff --git a/test/aroon.c b/test/aroon.c
new file mode 100644
index 0000000..df68637
--- /dev/null
+++ b/test/aroon.c
@@ -0,0 +1,21 @@
+#include "ts.h"
+
+bool ts_test_aroon( void )
+{
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
+ fp_t const c[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ /* k=3, offset=2, y_n=8 */
+ /* Monotonically rising H, falling L => most recent bar is always the extreme => age=0 => Aroon=100 */
+ /* Function writes to y[2..9], ts_test compares y[j+offset] vs e[j] for j=0..7 */
+ fp_t const eup[] = { 100, 100, 100, 100, 100, 100, 100, 100 };
+ fp_t const edn[] = { 100, 100, 100, 100, 100, 100, 100, 100 };
+ fp_t const * const exp[2] = { eup, edn };
+
+ uint_t k = 3;
+ return ts_test(ts_aroon, ts_aroon_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_aroon_p);
+}
diff --git a/test/aroon_oscillator.c b/test/aroon_oscillator.c
new file mode 100644
index 0000000..6a56922
--- /dev/null
+++ b/test/aroon_oscillator.c
@@ -0,0 +1,22 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_aroon_oscillator( void )
+{
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
+ fp_t const c[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ /* k=3, offset=2 */
+ /* Rising highs, falling lows => AroonUp=100, AroonDown=100 */
+ /* Oscillator = 100 - 100 = 0 */
+ fp_t const e[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_aroon_oscillator, ts_aroon_oscillator_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_aroon_oscillator_p);
+}
diff --git a/test/asi.c b/test/asi.c
new file mode 100644
index 0000000..0307a3b
--- /dev/null
+++ b/test/asi.c
@@ -0,0 +1,35 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_asi( void )
+{
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
+ fp_t const c[] = { 11, 12, 11, 14, 13, 16, 15, 18, 17, 20 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ /* ASI = prev_ASI + component2 + component3
+ * component2 = (term1 - term2 + term3) / denom * (H - L)
+ * component3 = (term1 - term2 + term3) / (2 * denom)
+ * term1 = C - Cprev, term2 = 0.5*(O - Op), term3 = 0.25*(C - O + Op - Cp)
+ * denom = max(H - Lp, Hp - L, Hp - Lp)
+ *
+ * i=0: ASI = 0
+ * i=1: term1=1, term2=0.5, term3=0, denom=4, comp2=0.375, comp3=0.125 => 0.5
+ * i=2: term1=-1, term2=0.5, term3=-0.5, denom=4, comp2=-1.5, comp3=-0.5 => -1.5
+ * i=3: term1=3, term2=0.5, term3=0.5, denom=4, comp2=2.25, comp3=0.75 => 1.5
+ * i=4: term1=-1, term2=0.5, term3=-0.5, denom=4, comp2=-1.5, comp3=-0.5 => -0.5
+ * i=5: term1=3, term2=0.5, term3=0.5, denom=4, comp2=2.25, comp3=0.75 => 2.5
+ * i=6: term1=-1, term2=0.5, term3=-0.5, denom=4, comp2=-1.5, comp3=-0.5 => 0.5
+ * i=7: term1=3, term2=0.5, term3=0.5, denom=4, comp2=2.25, comp3=0.75 => 3.5
+ * i=8: term1=-1, term2=0.5, term3=-0.5, denom=4, comp2=-1.5, comp3=-0.5 => 1.5
+ * i=9: term1=3, term2=0.5, term3=0.5, denom=4, comp2=2.25, comp3=0.75 => 4.5
+ */
+ fp_t const e[] = { 0, 0.5, -1.5, 1.5, -0.5, 2.5, 0.5, 3.5, 1.5, 4.5 };
+ fp_t const * const exp[1] = { e };
+
+ return ts_test(ts_asi, ts_asi_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)NULL, &ts_asi_p);
+}
diff --git a/test/asinh.c b/test/asinh.c
new file mode 100644
index 0000000..4ad6494
--- /dev/null
+++ b/test/asinh.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_asinh( void )
+{
+ fp_t const x0[] = { -2.000000, -1.000000, 0.000000, 1.000000, 2.000000 };
+ fp_t const e0[] = { -1.443635, -0.881374, 0.000000, 0.881374, 1.443635 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_asinh, ts_asinh_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_asinh_p);
+}
diff --git a/test/atan2.c b/test/atan2.c
new file mode 100644
index 0000000..8509e22
--- /dev/null
+++ b/test/atan2.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_atan2( void )
+{
+ fp_t const x0[] = { 0.000000, 1.000000, 1.000000, -1.000000 };
+ fp_t const x1[] = { -1.000000, 0.000000, -1.000000, 0.000000 };
+ fp_t const e0[] = { 3.141593, 1.570796, 2.356194, -1.570796 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_atan2, ts_atan2_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_atan2_p);
+}
diff --git a/test/atanh.c b/test/atanh.c
new file mode 100644
index 0000000..91a9169
--- /dev/null
+++ b/test/atanh.c
@@ -0,0 +1,35 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_atanh( void )
+{
+ fp_t const x0[] = { -0.900000, -0.500000, 0.000000, 0.500000, 0.900000 };
+ fp_t const e0[] = { -1.472219, -0.549306, 0.000000, 0.549306, 1.472219 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ bool result = ts_test(ts_atanh, ts_atanh_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_atanh_p);
+
+ fp_t const xb[] = { -1.000000, 1.000000, -1.500000, 1.500000 };
+ fp_t const eb[] = { -INFINITY, INFINITY, NAN, NAN };
+
+ for( uint_t i = 0; i < sizeof( xb ) / sizeof( *xb ); ++i )
+ {
+ fp_t tmp = 0;
+ fp_t const * const xi[1] = { &xb[i] };
+ fp_t * const yi[1] = { &tmp };
+ ts_atanh(yi, 1, xi, 1, 1, NULL);
+ if( isinf( eb[i] ) )
+ {
+ result &= isinf( tmp ) && signbit( tmp ) == signbit( eb[i] );
+ }
+ else if( isnan( eb[i] ) )
+ {
+ result &= isnan( tmp );
+ }
+ }
+
+ return result;
+}
diff --git a/test/atr.c b/test/atr.c
new file mode 100644
index 0000000..fb1ad4d
--- /dev/null
+++ b/test/atr.c
@@ -0,0 +1,19 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_atr( void )
+{
+ fp_t const o[] = { 9, 11, 10, 12, 12, 13, 11, 14, 13, 15 };
+ fp_t const h[] = { 10, 12, 11, 13, 12, 14, 12, 15, 14, 16 };
+ fp_t const l[] = { 8, 9, 10, 11, 10, 12, 11, 13, 12, 14 };
+ fp_t const c[] = { 9, 10, 12, 11, 13, 11, 14, 12, 15, 13 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ fp_t const e[] = { 2.0, 2.0, 2.0, 1.66667, 1.77778, 1.85185, 1.90123 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_atr, ts_atr_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_atr_p);
+}
diff --git a/test/atr_bands.c b/test/atr_bands.c
new file mode 100644
index 0000000..2d58743
--- /dev/null
+++ b/test/atr_bands.c
@@ -0,0 +1,29 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_atr_bands( void )
+{
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
+ fp_t const cp[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+
+ fp_t const * const x[] = { o, h, l, cp };
+
+ /* k=3, mult=2.0 */
+ /* TR[1]=3, TR[2]=3, TR[3]=3 => ATR=3.0 */
+ /* TR[4]=3, ... => ATR stays 3.0 */
+ /* Middle(SMA3 of close from idx 1): (12+13+14)/3=13, then rolling */
+ /* Middle: 13, 14, 15, 16, 17, 18, 19 */
+ /* Upper: 19, 20, 21, 22, 23, 24, 25 */
+ /* Lower: 7, 8, 9, 10, 11, 12, 13 */
+ fp_t const em[] = { 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const eu[] = { 19, 20, 21, 22, 23, 24, 25 };
+ fp_t const el[] = { 7, 8, 9, 10, 11, 12, 13 };
+ fp_t const * const exp[3] = { eu, em, el };
+
+ typedef struct { uint_t k; fp_t mult; } c_t;
+ c_t c = { .k = 3, .mult = 2.0 };
+ return ts_test(ts_atr_bands, ts_atr_bands_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&c, &ts_atr_bands_p);
+}
diff --git a/test/atr_trailing_stops.c b/test/atr_trailing_stops.c
new file mode 100644
index 0000000..5f04b14
--- /dev/null
+++ b/test/atr_trailing_stops.c
@@ -0,0 +1,26 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_atr_trailing_stops( void )
+{
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
+ fp_t const cp[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+
+ fp_t const * const x[] = { o, h, l, cp };
+
+ /* k=3, mult=2.0, offset=3, y_n=7 */
+ /* TR: all 3 => ATR=3.0 */
+ /* At i=3: Mid=(15+12)/2=13.5, Upper=13.5+6=19.5, Lower=13.5-6=7.5 */
+ /* At i=4: Mid=14.5, computed U=20.5, L=8.5 => trail: U=fmax(19.5,20.5)=20.5, L=fmin(7.5,8.5)=7.5 */
+ /* Upper trails up, lower stays at 7.5 (only moves down) */
+ fp_t const eu[] = { 19.5, 20.5, 21.5, 22.5, 23.5, 24.5, 25.5 };
+ fp_t const el[] = { 7.5, 7.5, 7.5, 7.5, 7.5, 7.5, 7.5 };
+ fp_t const * const exp[2] = { eu, el };
+
+ typedef struct { uint_t k; fp_t mult; } c_t;
+ c_t my_c = { .k = 3, .mult = 2.0 };
+ return ts_test(ts_atr_trailing_stops, ts_atr_trailing_stops_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&my_c, &ts_atr_trailing_stops_p);
+}
diff --git a/test/average.c b/test/average.c
new file mode 100644
index 0000000..8652a99
--- /dev/null
+++ b/test/average.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_average( void )
+{
+ uint_t const c = 3;
+
+ fp_t const x0[] = { 2, 4, 6, 8, 10 };
+ fp_t const e0[] = { 4, 6, 8 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_average, ts_average_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_average_p);
+}
diff --git a/test/average_cumulative.c b/test/average_cumulative.c
new file mode 100644
index 0000000..01986f7
--- /dev/null
+++ b/test/average_cumulative.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_average_cumulative( void )
+{
+ fp_t const x0[] = { -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+ fp_t const e0[] = { -10, -9.5, -9, -8.5, -8, -7.5, -7, -6.5, -6, -5.5, -5, -4.5, -4, -3.5, -3, -2.5, -2, -1.5, -1, -0.5, 0 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_average_cumulative, ts_average_cumulative_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_average_cumulative_p);
+}
diff --git a/test/average_exponential.c b/test/average_exponential.c
new file mode 100644
index 0000000..2e0b50b
--- /dev/null
+++ b/test/average_exponential.c
@@ -0,0 +1,16 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_average_exponential( void )
+{
+ fp_t const x0[] = { -10, -7.5, -5, -2.5, 0, 2.5, 5, 7.5, 10 };
+ fp_t const e0[] = { -10, -8.75, -6.875, -4.6875, -2.34375, 0.078125, 2.5390625, 5.01953125, 7.509765625 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ fp_t c = 2;
+
+ return ts_test(ts_average_exponential, ts_average_exponential_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_average_exponential_p);
+}
diff --git a/test/average_exponential_double.c b/test/average_exponential_double.c
new file mode 100644
index 0000000..f78d341
--- /dev/null
+++ b/test/average_exponential_double.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_average_exponential_double( void )
+{
+ fp_t const c[] = { 0.5 };
+
+ fp_t const x0[] = { 1, 2, 3, 4, 5 };
+ fp_t const e0[] = { 1, 1.75, 2.75, 3.8125, 4.875 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_average_exponential_double, ts_average_exponential_double_offset, x, e, sizeof( c ) / sizeof( *c ), (ts_constant_t *)&c, &ts_average_exponential_double_p);
+}
diff --git a/test/average_exponential_triple.c b/test/average_exponential_triple.c
new file mode 100644
index 0000000..baeeb31
--- /dev/null
+++ b/test/average_exponential_triple.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_average_exponential_triple( void )
+{
+ fp_t const c[] = { 0.5 };
+
+ fp_t const x0[] = { 1, 2, 3, 4, 5 };
+ fp_t const e0[] = { 1, 1.875, 2.9375, 4.0, 5.03125 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_average_exponential_triple, ts_average_exponential_triple_offset, x, e, sizeof( c ) / sizeof( *c ), (ts_constant_t *)&c, &ts_average_exponential_triple_p);
+}
diff --git a/test/average_price.c b/test/average_price.c
new file mode 100644
index 0000000..2587783
--- /dev/null
+++ b/test/average_price.c
@@ -0,0 +1,17 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_average_price( void )
+{
+ fp_t const x0[] = { 1, 1, 1, 1, 1 };
+ fp_t const x1[] = { 2, 3, 4, 5, 6 };
+ fp_t const x2[] = { 0, 1, 2, 3, 4 };
+ fp_t const x3[] = { 3, 4, 5, 6, 7 };
+ fp_t const x4[] = { 1, 1, 1, 1, 1 };
+ fp_t const e0[] = { 1.66666666666667, 2.66666666666667, 3.66666666666667, 4.66666666666667, 5.66666666666667 };
+
+ fp_t const * const x[] = { x0, x1, x2, x3, x4 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_average_price, ts_average_price_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_average_price_p);
+}
diff --git a/test/average_triangle.c b/test/average_triangle.c
new file mode 100644
index 0000000..7116099
--- /dev/null
+++ b/test/average_triangle.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_average_triangle( void )
+{
+ uint_t const c[] = { 3 };
+
+ fp_t const x0[] = { 1, 2, 3, 4, 5, 6 };
+ fp_t const e0[] = { 3, 4 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_average_triangle, ts_average_triangle_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_average_triangle_p);
+}
diff --git a/test/average_weighted.c b/test/average_weighted.c
new file mode 100644
index 0000000..544af84
--- /dev/null
+++ b/test/average_weighted.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_average_weighted( void )
+{
+ uint_t const c[] = { 3 };
+
+ fp_t const x0[] = { 1, 2, 3, 4, 5 };
+ fp_t const e0[] = { 2.333333, 3.333333, 4.333333 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_average_weighted, ts_average_weighted_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_average_weighted_p);
+}
diff --git a/test/average_weighted_series.c b/test/average_weighted_series.c
new file mode 100644
index 0000000..f6c4318
--- /dev/null
+++ b/test/average_weighted_series.c
@@ -0,0 +1,16 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_average_weighted_series( void )
+{
+ uint_t const c[] = { 3 };
+
+ fp_t const x0[] = { 1, 2, 3, 4, 5 };
+ fp_t const x1[] = { 2, 3, 4, 5, 6 };
+ fp_t const e0[] = { 6.666667, 12.666667, 20.666667 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_average_weighted_series, ts_average_weighted_series_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_average_weighted_series_p);
+}
diff --git a/test/awesome_oscillator.c b/test/awesome_oscillator.c
new file mode 100644
index 0000000..205bf78
--- /dev/null
+++ b/test/awesome_oscillator.c
@@ -0,0 +1,58 @@
+#include <stddef.h>
+#include <stdlib.h>
+#include <math.h>
+#include "ts.h"
+
+bool ts_test_awesome_oscillator( void )
+{
+ uint_t n = 40;
+ fp_t *o = calloc( n, sizeof( fp_t ) );
+ fp_t *h = calloc( n, sizeof( fp_t ) );
+ fp_t *l = calloc( n, sizeof( fp_t ) );
+ fp_t *c = calloc( n, sizeof( fp_t ) );
+
+ if ( !o || !h || !l || !c )
+ {
+ free( o ); free( h ); free( l ); free( c );
+ return false;
+ }
+
+ for ( uint_t i = 0; i < n; ++i )
+ {
+ o[i] = (fp_t)(10 + i);
+ h[i] = (fp_t)(12 + i);
+ l[i] = (fp_t)(8 + i);
+ c[i] = (fp_t)(11 + i);
+ }
+
+ fp_t *result = calloc( n, sizeof( fp_t ) );
+
+ ts_awesome_oscillator(&result, 1, (fp_t const * const []){ o, h, l, c }, 4, n, NULL);
+
+ /* Compute expected EMA(5) and EMA(34) of median price */
+ fp_t fast_a = 2.0 / 6.0;
+ fp_t slow_a = 2.0 / 35.0;
+ fp_t sum_fast = 0, sum_slow = 0;
+ for ( uint_t i = 0; i < 34; ++i )
+ {
+ fp_t mp = (h[i] + l[i]) / 2;
+ sum_slow += mp;
+ if ( i < 5 ) sum_fast += mp;
+ }
+ fp_t ema_fast = sum_fast / 5.0;
+ fp_t ema_slow = sum_slow / 34.0;
+ for ( uint_t i = 34; i < n; ++i )
+ {
+ fp_t mp = (h[i] + l[i]) / 2;
+ ema_fast = ema_fast + fast_a * ( mp - ema_fast );
+ ema_slow = ema_slow + slow_a * ( mp - ema_slow );
+ }
+
+ fp_t expected = ema_fast - ema_slow;
+
+ bool pass = fabs( result[n - 1] - expected ) < 1e-10;
+
+ free( o ); free( h ); free( l ); free( c ); free( result );
+
+ return pass;
+}
diff --git a/test/balance_of_power.c b/test/balance_of_power.c
new file mode 100644
index 0000000..cff39bb
--- /dev/null
+++ b/test/balance_of_power.c
@@ -0,0 +1,17 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_balance_of_power( void )
+{
+ fp_t const x0[] = { 10, 10, 15, 20, 25 };
+ fp_t const x1[] = { 14, 13, 18, 24, 29 };
+ fp_t const x2[] = { 8, 9, 14, 21, 24 };
+ fp_t const x3[] = { 12, 11, 16, 22, 27 };
+ fp_t const e0[] = { 0.333333333333333, 0.25, 0.25, 0.666666666666667, 0.4 };
+
+ fp_t const * const x[] = { x0, x1, x2, x3 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_balance_of_power, ts_balance_of_power_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_balance_of_power_p);
+}
diff --git a/test/band_width.c b/test/band_width.c
new file mode 100644
index 0000000..99dd4bf
--- /dev/null
+++ b/test/band_width.c
@@ -0,0 +1,19 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_band_width( void )
+{
+ fp_t const x[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const * const inp[1] = { x };
+
+ /* k=3, mult=2.0 */
+ /* SD = sqrt(2/3) = 0.81650 for all windows */
+ /* Width = (Upper - Lower) / Middle * 100 = (4*SD) / Middle * 100 */
+ fp_t const e[] = { 29.690785, 27.216553, 25.122972, 23.328474, 21.773242, 20.412415, 19.211684, 18.144368 };
+ fp_t const * const exp[1] = { e };
+
+ typedef struct { uint_t k; fp_t std_mult; } c_t;
+ c_t my_c = { .k = 3, .std_mult = 2.0 };
+ return ts_test(ts_band_width, ts_band_width_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&my_c, &ts_band_width_p);
+}
diff --git a/test/bench.c b/test/bench.c
new file mode 100644
index 0000000..83330aa
--- /dev/null
+++ b/test/bench.c
@@ -0,0 +1,204 @@
+#define TS_IMPLEMENTATION
+#include "ts.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#define BENCH_N 10000
+#define BENCH_ITER 100
+
+typedef struct { uint_t k; fp_t std_mult; } bb_params_t;
+typedef struct { fp_t fast_k; fp_t slow_k; fp_t signal_k; } macd_params_t;
+typedef struct { fp_t Q; fp_t R; } kalman_params_t;
+
+static fp_t bench_input[BENCH_N];
+static fp_t bench_output[BENCH_N];
+static fp_t bench_ohlc[4][BENCH_N];
+static fp_t bench_output_multi[4][BENCH_N];
+
+static double elapsed_ms(struct timespec *start, struct timespec *end)
+{
+ return (end->tv_sec - start->tv_sec) * 1000.0 + (end->tv_nsec - start->tv_nsec) / 1000000.0;
+}
+
+int main(void)
+{
+ ts_fill(bench_input, BENCH_N, 0, BENCH_N);
+
+ for (uint_t c = 0; c < 4; c++) {
+ ts_fill(bench_ohlc[c], BENCH_N, 100 + c, 200 + c);
+ }
+
+ printf("Benchmark: n=%d, iterations=%d\n", BENCH_N, BENCH_ITER);
+ printf("--------------------------------------------------\n");
+
+ struct timespec start, end;
+
+ /* ts_average */
+ {
+ uint_t k = 14;
+ fp_t * const y[1] = { bench_output };
+ fp_t const * const x[1] = { bench_input };
+ clock_gettime(CLOCK_MONOTONIC, &start);
+ for (uint_t i = 0; i < BENCH_ITER; i++) {
+ (void)ts_average(y, 1, x, 1, BENCH_N, (ts_constant_t *)&k);
+ }
+ clock_gettime(CLOCK_MONOTONIC, &end);
+ double ms = elapsed_ms(&start, &end);
+ printf("%-40s %8.2f ms (%.0f ns/call)\n", "ts_average", ms, ms * 1000000.0 / BENCH_ITER);
+ }
+
+ /* ts_rsi */
+ {
+ uint_t k = 14;
+ fp_t * const y[1] = { bench_output };
+ fp_t const * const x[1] = { bench_input };
+ clock_gettime(CLOCK_MONOTONIC, &start);
+ for (uint_t i = 0; i < BENCH_ITER; i++) {
+ (void)ts_rsi(y, 1, x, 1, BENCH_N, (ts_constant_t *)&k);
+ }
+ clock_gettime(CLOCK_MONOTONIC, &end);
+ double ms = elapsed_ms(&start, &end);
+ printf("%-40s %8.2f ms (%.0f ns/call)\n", "ts_rsi", ms, ms * 1000000.0 / BENCH_ITER);
+ }
+
+ /* ts_deviation_standard */
+ {
+ uint_t k = 14;
+ fp_t * const y[1] = { bench_output };
+ fp_t const * const x[1] = { bench_input };
+ clock_gettime(CLOCK_MONOTONIC, &start);
+ for (uint_t i = 0; i < BENCH_ITER; i++) {
+ (void)ts_deviation_standard(y, 1, x, 1, BENCH_N, (ts_constant_t *)&k);
+ }
+ clock_gettime(CLOCK_MONOTONIC, &end);
+ double ms = elapsed_ms(&start, &end);
+ printf("%-40s %8.2f ms (%.0f ns/call)\n", "ts_deviation_standard", ms, ms * 1000000.0 / BENCH_ITER);
+ }
+
+ /* ts_average_exponential */
+ {
+ uint_t k = 14;
+ fp_t * const y[1] = { bench_output };
+ fp_t const * const x[1] = { bench_input };
+ clock_gettime(CLOCK_MONOTONIC, &start);
+ for (uint_t i = 0; i < BENCH_ITER; i++) {
+ (void)ts_average_exponential(y, 1, x, 1, BENCH_N, (ts_constant_t *)&k);
+ }
+ clock_gettime(CLOCK_MONOTONIC, &end);
+ double ms = elapsed_ms(&start, &end);
+ printf("%-40s %8.2f ms (%.0f ns/call)\n", "ts_average_exponential", ms, ms * 1000000.0 / BENCH_ITER);
+ }
+
+ /* ts_bollinger_bands */
+ {
+ bb_params_t c = { .k = 20, .std_mult = 2.0 };
+ fp_t * const y[3] = { bench_output_multi[0], bench_output_multi[1], bench_output_multi[2] };
+ fp_t const * const x[1] = { bench_input };
+ clock_gettime(CLOCK_MONOTONIC, &start);
+ for (uint_t i = 0; i < BENCH_ITER; i++) {
+ (void)ts_bollinger_bands(y, 3, x, 1, BENCH_N, (ts_constant_t *)&c);
+ }
+ clock_gettime(CLOCK_MONOTONIC, &end);
+ double ms = elapsed_ms(&start, &end);
+ printf("%-40s %8.2f ms (%.0f ns/call)\n", "ts_bollinger_bands", ms, ms * 1000000.0 / BENCH_ITER);
+ }
+
+ /* ts_macd */
+ {
+ macd_params_t c = { .fast_k = 12, .slow_k = 26, .signal_k = 9 };
+ fp_t * const y[3] = { bench_output_multi[0], bench_output_multi[1], bench_output_multi[2] };
+ fp_t const * const x[1] = { bench_input };
+ clock_gettime(CLOCK_MONOTONIC, &start);
+ for (uint_t i = 0; i < BENCH_ITER; i++) {
+ (void)ts_macd(y, 3, x, 1, BENCH_N, (ts_constant_t *)&c);
+ }
+ clock_gettime(CLOCK_MONOTONIC, &end);
+ double ms = elapsed_ms(&start, &end);
+ printf("%-40s %8.2f ms (%.0f ns/call)\n", "ts_macd", ms, ms * 1000000.0 / BENCH_ITER);
+ }
+
+ /* ts_atr */
+ {
+ uint_t k = 14;
+ fp_t * const y[1] = { bench_output };
+ fp_t const * const x[4] = { bench_ohlc[0], bench_ohlc[1], bench_ohlc[2], bench_ohlc[3] };
+ clock_gettime(CLOCK_MONOTONIC, &start);
+ for (uint_t i = 0; i < BENCH_ITER; i++) {
+ (void)ts_atr(y, 1, x, 4, BENCH_N, (ts_constant_t *)&k);
+ }
+ clock_gettime(CLOCK_MONOTONIC, &end);
+ double ms = elapsed_ms(&start, &end);
+ printf("%-40s %8.2f ms (%.0f ns/call)\n", "ts_atr", ms, ms * 1000000.0 / BENCH_ITER);
+ }
+
+ /* ts_adx */
+ {
+ uint_t k = 14;
+ fp_t * const y[4] = { bench_output_multi[0], bench_output_multi[1], bench_output_multi[2], bench_output_multi[3] };
+ fp_t const * const x[4] = { bench_ohlc[0], bench_ohlc[1], bench_ohlc[2], bench_ohlc[3] };
+ clock_gettime(CLOCK_MONOTONIC, &start);
+ for (uint_t i = 0; i < BENCH_ITER; i++) {
+ (void)ts_adx(y, 4, x, 4, BENCH_N, (ts_constant_t *)&k);
+ }
+ clock_gettime(CLOCK_MONOTONIC, &end);
+ double ms = elapsed_ms(&start, &end);
+ printf("%-40s %8.2f ms (%.0f ns/call)\n", "ts_adx", ms, ms * 1000000.0 / BENCH_ITER);
+ }
+
+ /* ts_kalman_filter */
+ {
+ kalman_params_t c = { .Q = 0.01, .R = 0.05 };
+ fp_t * const y[1] = { bench_output };
+ fp_t const * const x[1] = { bench_input };
+ clock_gettime(CLOCK_MONOTONIC, &start);
+ for (uint_t i = 0; i < BENCH_ITER; i++) {
+ (void)ts_kalman_filter(y, 1, x, 1, BENCH_N, (ts_constant_t *)&c);
+ }
+ clock_gettime(CLOCK_MONOTONIC, &end);
+ double ms = elapsed_ms(&start, &end);
+ printf("%-40s %8.2f ms (%.0f ns/call)\n", "ts_kalman_filter", ms, ms * 1000000.0 / BENCH_ITER);
+ }
+
+ /* ts_absolute_value */
+ {
+ fp_t * const y[1] = { bench_output };
+ fp_t const * const x[1] = { bench_input };
+ clock_gettime(CLOCK_MONOTONIC, &start);
+ for (uint_t i = 0; i < BENCH_ITER; i++) {
+ (void)ts_absolute_value(y, 1, x, 1, BENCH_N, NULL);
+ }
+ clock_gettime(CLOCK_MONOTONIC, &end);
+ double ms = elapsed_ms(&start, &end);
+ printf("%-40s %8.2f ms (%.0f ns/call)\n", "ts_absolute_value", ms, ms * 1000000.0 / BENCH_ITER);
+ }
+
+ /* ts_sine */
+ {
+ fp_t * const y[1] = { bench_output };
+ fp_t const * const x[1] = { bench_input };
+ clock_gettime(CLOCK_MONOTONIC, &start);
+ for (uint_t i = 0; i < BENCH_ITER; i++) {
+ (void)ts_sine(y, 1, x, 1, BENCH_N, NULL);
+ }
+ clock_gettime(CLOCK_MONOTONIC, &end);
+ double ms = elapsed_ms(&start, &end);
+ printf("%-40s %8.2f ms (%.0f ns/call)\n", "ts_sine", ms, ms * 1000000.0 / BENCH_ITER);
+ }
+
+ /* ts_addition */
+ {
+ fp_t * const y[1] = { bench_output };
+ fp_t const * const x[2] = { bench_input, bench_ohlc[0] };
+ clock_gettime(CLOCK_MONOTONIC, &start);
+ for (uint_t i = 0; i < BENCH_ITER; i++) {
+ (void)ts_addition(y, 1, x, 2, BENCH_N, NULL);
+ }
+ clock_gettime(CLOCK_MONOTONIC, &end);
+ double ms = elapsed_ms(&start, &end);
+ printf("%-40s %8.2f ms (%.0f ns/call)\n", "ts_addition", ms, ms * 1000000.0 / BENCH_ITER);
+ }
+
+ return 0;
+}
diff --git a/test/beta_pdf.c b/test/beta_pdf.c
new file mode 100644
index 0000000..d8606b9
--- /dev/null
+++ b/test/beta_pdf.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_beta_pdf( void )
+{
+ fp_t const c[] = { 2.000000, 5.000000 };
+
+ fp_t const x0[] = { 0.100000, 0.200000, 0.300000, 0.400000, 0.500000 };
+ fp_t const e0[] = { 1.968300, 2.457600, 2.160900, 1.555200, 0.937500 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_beta_pdf, ts_beta_pdf_offset, x, e, sizeof( c ) / sizeof( *c ), (ts_constant_t *)&c, &ts_beta_pdf_p);
+}
diff --git a/test/binomial.c b/test/binomial.c
new file mode 100644
index 0000000..365ed6d
--- /dev/null
+++ b/test/binomial.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_binomial( void )
+{
+ fp_t const x0[] = { 5.000000, 10.000000, 6.000000, 4.000000 };
+ fp_t const x1[] = { 2.000000, 3.000000, 2.000000, 2.000000 };
+ fp_t const e0[] = { 10.000000, 120.000000, 15.000000, 6.000000 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_binomial, ts_binomial_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_binomial_p);
+}
diff --git a/test/binomial_pdf.c b/test/binomial_pdf.c
new file mode 100644
index 0000000..2d234cc
--- /dev/null
+++ b/test/binomial_pdf.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_binomial_pdf( void )
+{
+ /* n_trials=10 (uint_t at byte 0), p=0.5 (fp_t at byte 8) */
+ typedef struct { uint_t n_trials; fp_t p; } c_t;
+ c_t c = { .n_trials = 10, .p = 0.5 };
+ fp_t const x0[] = { 0,1,2,3,4,5,6,7,8,9,10 };
+ fp_t const e0[] = { 0.000977, 0.009766, 0.043945, 0.117188, 0.205078, 0.246094, 0.205078, 0.117188, 0.043945, 0.009766, 0.000977 };
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+ return ts_test(ts_binomial_pdf, ts_binomial_pdf_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_binomial_pdf_p);
+}
diff --git a/test/bollinger_bands.c b/test/bollinger_bands.c
new file mode 100644
index 0000000..37bc138
--- /dev/null
+++ b/test/bollinger_bands.c
@@ -0,0 +1,22 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_bollinger_bands( void )
+{
+ fp_t const x[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const * const inp[1] = { x };
+
+ /* k=3, std_mult=2 */
+ /* SMA: 11, 12, 13, 14, 15, 16, 17, 18 */
+ /* SD: sqrt(2/3)=0.81650 for all windows */
+ /* Upper = SMA + 2*SD, Lower = SMA - 2*SD */
+ fp_t const em[] = { 11, 12, 13, 14, 15, 16, 17, 18 };
+ fp_t const eu[] = { 12.632993, 13.632993, 14.632993, 15.632993, 16.632993, 17.632993, 18.632993, 19.632993 };
+ fp_t const el[] = { 9.367007, 10.367007, 11.367007, 12.367007, 13.367007, 14.367007, 15.367007, 16.367007 };
+ fp_t const * const exp[3] = { em, eu, el };
+
+ typedef struct { uint_t k; fp_t std_mult; } c_t;
+ c_t c = { .k = 3, .std_mult = 2.0 };
+ return ts_test(ts_bollinger_bands, ts_bollinger_bands_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&c, &ts_bollinger_bands_p);
+}
diff --git a/test/bollinger_lower.c b/test/bollinger_lower.c
new file mode 100644
index 0000000..5e91f97
--- /dev/null
+++ b/test/bollinger_lower.c
@@ -0,0 +1,16 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_bollinger_lower( void )
+{
+ fp_t const x[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const * const inp[1] = { x };
+
+ fp_t const e[] = { 9.367007, 10.367007, 11.367007, 12.367007, 13.367007, 14.367007, 15.367007, 16.367007 };
+ fp_t const * const exp[1] = { e };
+
+ typedef struct { uint_t k; fp_t std_mult; } c_t;
+ c_t c = { .k = 3, .std_mult = 2.0 };
+ return ts_test(ts_bollinger_lower, ts_bollinger_lower_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&c, &ts_bollinger_lower_p);
+}
diff --git a/test/bollinger_pctb.c b/test/bollinger_pctb.c
new file mode 100644
index 0000000..760156f
--- /dev/null
+++ b/test/bollinger_pctb.c
@@ -0,0 +1,19 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_bollinger_pctb( void )
+{
+ fp_t const x[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const * const inp[1] = { x };
+
+ /* pctB = (C - Lower) / (Upper - Lower) = (C - (M - 2*SD)) / (4*SD) */
+ /* At idx 2: C=12, M=11, SD=0.81650, (12 - 9.367) / 3.266 = 0.80622 */
+ /* At idx 3: C=13, M=12, SD=0.81650, (13 - 10.367) / 3.266 = 0.80622 */
+ fp_t const e[] = { 0.8061862178, 0.8061862178, 0.8061862178, 0.8061862178, 0.8061862178, 0.8061862178, 0.8061862178, 0.8061862178 };
+ fp_t const * const exp[1] = { e };
+
+ typedef struct { uint_t k; fp_t std_mult; } c_t;
+ c_t c = { .k = 3, .std_mult = 2.0 };
+ return ts_test(ts_bollinger_pctb, ts_bollinger_pctb_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&c, &ts_bollinger_pctb_p);
+}
diff --git a/test/bollinger_upper.c b/test/bollinger_upper.c
new file mode 100644
index 0000000..701980e
--- /dev/null
+++ b/test/bollinger_upper.c
@@ -0,0 +1,16 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_bollinger_upper( void )
+{
+ fp_t const x[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const * const inp[1] = { x };
+
+ fp_t const e[] = { 12.632993, 13.632993, 14.632993, 15.632993, 16.632993, 17.632993, 18.632993, 19.632993 };
+ fp_t const * const exp[1] = { e };
+
+ typedef struct { uint_t k; fp_t std_mult; } c_t;
+ c_t c = { .k = 3, .std_mult = 2.0 };
+ return ts_test(ts_bollinger_upper, ts_bollinger_upper_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&c, &ts_bollinger_upper_p);
+}
diff --git a/test/bollinger_width.c b/test/bollinger_width.c
new file mode 100644
index 0000000..6a5b21f
--- /dev/null
+++ b/test/bollinger_width.c
@@ -0,0 +1,17 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_bollinger_width( void )
+{
+ fp_t const x[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const * const inp[1] = { x };
+
+ /* width = 2*2*SD/Middle*100 = 4*0.81650/Middle*100 */
+ fp_t const e[] = { 29.690785, 27.216553, 25.122972, 23.328474, 21.773242, 20.412415, 19.211684, 18.144368 };
+ fp_t const * const exp[1] = { e };
+
+ typedef struct { uint_t k; fp_t std_mult; } c_t;
+ c_t c = { .k = 3, .std_mult = 2.0 };
+ return ts_test(ts_bollinger_width, ts_bollinger_width_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&c, &ts_bollinger_width_p);
+}
diff --git a/test/candlestick_formations.c b/test/candlestick_formations.c
new file mode 100644
index 0000000..300b05e
--- /dev/null
+++ b/test/candlestick_formations.c
@@ -0,0 +1,32 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_candlestick_formations( void )
+{
+ fp_t const o[] = { 10, 10, 14, 11, 10, 15, 12, 13, 14, 16 };
+ fp_t const h[] = { 11, 11, 15, 12, 14, 15, 13, 16, 14, 16 };
+ fp_t const l[] = { 9, 9, 13, 10, 10, 13, 11, 12, 12, 14 };
+ fp_t const c[] = { 10, 10, 14, 11, 13, 14, 12, 15, 13, 15 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ /* i=1: O=10,C=10,H=11,L=9, body=0, range=2, doji (body/range=0 < 0.1) => 1 */
+ /* i=2: O=14,C=14,H=15,L=13, body=0, range=2, doji => 1 */
+ /* i=3: O=11,C=11,H=12,L=10, body=0, range=2, doji => 1 */
+ /* i=4: O=10,C=13,H=14,L=10, body=3, range=4, upper_wick=1, lower_wick=0, body_abs/range=0.75 */
+ /* prev_body=0, not engulfing. upper_wick=1, lower_wick=0 => not hammer/star => 0 */
+ /* i=5: O=15,C=14,H=15,L=13, body=-1, range=2, upper_wick=0, lower_wick=1 */
+ /* prev_body=3>0, -body=1 < 3, not engulfing. upper_wick=0 < 2*1 => not star. lower_wick=1 < 2*1 => not hammer. 0 */
+ /* i=6: O=12,C=12,H=13,L=11, body=0, range=2, doji => 1 */
+ /* i=7: O=13,C=15,H=16,L=12, body=2, range=4, upper_wick=1, lower_wick=1 */
+ /* prev_body=0, not engulfing. upper_wick=1 < 2*2, lower_wick=1 < 2*2 => 0 */
+ /* i=8: O=14,C=13,H=14,L=12, body=-1, range=2, upper_wick=0, lower_wick=1 */
+ /* prev_body=2>0, -body=1 < 2, not engulfing. not star/hammer => 0 */
+ /* i=9: O=16,C=15,H=16,L=14, body=-1, range=2, upper_wick=0, lower_wick=1 */
+ /* prev_body=-1, same direction, not engulfing. lower_wick=1 < 2*1 => 0 */
+ fp_t const e[] = { 1, 1, 1, 0, 0, 1, 0, 0, 0 };
+ fp_t const * const exp[1] = { e };
+
+ return ts_test(ts_candlestick_formations, ts_candlestick_formations_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)NULL, &ts_candlestick_formations_p);
+}
diff --git a/test/catalan.c b/test/catalan.c
new file mode 100644
index 0000000..0dda06e
--- /dev/null
+++ b/test/catalan.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_catalan( void )
+{
+ fp_t const x0[] = { 0.000000, 1.000000, 2.000000, 3.000000, 4.000000, 5.000000 };
+ fp_t const e0[] = { 1.000000, 1.000000, 2.000000, 5.000000, 14.000000, 42.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_catalan, ts_catalan_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_catalan_p);
+}
diff --git a/test/cauchy_cdf.c b/test/cauchy_cdf.c
new file mode 100644
index 0000000..0197133
--- /dev/null
+++ b/test/cauchy_cdf.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_cauchy_cdf( void )
+{
+ fp_t const x0[] = { -2.000000, -1.000000, 0.000000, 1.000000, 2.000000 };
+ fp_t const e0[] = { 0.147584, 0.250000, 0.500000, 0.750000, 0.852416 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_cauchy_cdf, ts_cauchy_cdf_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_cauchy_cdf_p);
+}
diff --git a/test/cauchy_pdf.c b/test/cauchy_pdf.c
new file mode 100644
index 0000000..e8c5f55
--- /dev/null
+++ b/test/cauchy_pdf.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_cauchy_pdf( void )
+{
+ fp_t const x0[] = { -2.000000, -1.000000, 0.000000, 1.000000, 2.000000 };
+ fp_t const e0[] = { 0.063662, 0.159155, 0.318310, 0.159155, 0.063662 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_cauchy_pdf, ts_cauchy_pdf_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_cauchy_pdf_p);
+}
diff --git a/test/cbrt.c b/test/cbrt.c
new file mode 100644
index 0000000..f0c0ae4
--- /dev/null
+++ b/test/cbrt.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_cbrt( void )
+{
+ fp_t const x0[] = { -8.000000, -1.000000, 0.000000, 1.000000, 8.000000, 27.000000 };
+ fp_t const e0[] = { -2.000000, -1.000000, 0.000000, 1.000000, 2.000000, 3.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_cbrt, ts_cbrt_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_cbrt_p);
+}
diff --git a/test/cci.c b/test/cci.c
new file mode 100644
index 0000000..21fb97c
--- /dev/null
+++ b/test/cci.c
@@ -0,0 +1,26 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_cci( void )
+{
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
+ fp_t const c[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ /* k=3
+ * TP: 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
+ * SMA(3) of TP: 12, 13, 14, 15, 16, 17, 18, 19
+ * MAD(3) of TP around SMA: |11-12|+|12-12|+|13-12| / 3 = 2/3 for first window
+ * CCI = (13 - 12) / (0.015 * 2/3) = 1 / 0.01 = 100
+ * Next: SMA=13, MAD = (|12-13|+|13-13|+|14-13|)/3 = 2/3, CCI = (14-13)/(0.015*2/3) = 100
+ */
+ fp_t const e[] = { 100, 100, 100, 100, 100, 100, 100, 100 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_cci, ts_cci_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_cci_p);
+}
diff --git a/test/ceiling.c b/test/ceiling.c
new file mode 100644
index 0000000..dece6c0
--- /dev/null
+++ b/test/ceiling.c
@@ -0,0 +1,14 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_ceiling( void )
+{
+ fp_t const x0[] = { -4.4, -3.2, -2.2, -1.1, 0, 1.1, 2.2, 3.3, 4.4 };
+ fp_t const e0[] = { -4, -3, -2, -1, 0, 2, 3, 4, 5 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_ceiling, ts_ceiling_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_ceiling_p);
+}
diff --git a/test/center_of_gravity.c b/test/center_of_gravity.c
new file mode 100644
index 0000000..ee67df3
--- /dev/null
+++ b/test/center_of_gravity.c
@@ -0,0 +1,17 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_center_of_gravity( void )
+{
+ fp_t const x[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+ fp_t const * const inp[1] = { x };
+
+ /* k=5: windows [1,2,3,4,5], [2,3,4,5,6], [3,4,5,6,7], [4,5,6,7,8]
+ * COG = 1 - 2*SUM(ix)/((k+1)*SUM(x)) */
+ fp_t const e[] = { -0.22222, -0.16667, -0.13333, -0.11111 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 5;
+ return ts_test(ts_center_of_gravity, ts_center_of_gravity_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_center_of_gravity_p);
+}
diff --git a/test/chande_forecast_oscillator.c b/test/chande_forecast_oscillator.c
new file mode 100644
index 0000000..3dad081
--- /dev/null
+++ b/test/chande_forecast_oscillator.c
@@ -0,0 +1,15 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_chande_forecast_oscillator( void )
+{
+ fp_t const x[] = { 1, 3, 2, 5, 4, 7, 6, 8 };
+ fp_t const * const inp[1] = { x };
+
+ fp_t const e[] = { -25.9259259259259, -2.7777777777778, -23.0769230769231, -4.7619047619048 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 5;
+ return ts_test(ts_chande_forecast_oscillator, ts_chande_forecast_oscillator_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_chande_forecast_oscillator_p);
+}
diff --git a/test/change.c b/test/change.c
new file mode 100644
index 0000000..39725e2
--- /dev/null
+++ b/test/change.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_change( void )
+{
+ fp_t const x0[] = { 10, 12, 14, 16, 18, 20, 22, 24 };
+ fp_t const e0[] = { 60, 50, 42.8571428571429, 37.5, 33.3333333333333 };
+
+ fp_t const * const x[] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ uint_t const k = 3;
+
+ return ts_test(ts_change, ts_change_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&k, &ts_change_p);
+}
diff --git a/test/channel.c b/test/channel.c
new file mode 100644
index 0000000..edbed8f
--- /dev/null
+++ b/test/channel.c
@@ -0,0 +1,23 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_channel( void )
+{
+ fp_t const o[] = { 10, 12, 11, 14, 13, 15, 16, 14, 17, 18 };
+ fp_t const h[] = { 12, 14, 13, 16, 15, 17, 18, 16, 19, 20 };
+ fp_t const l[] = { 9, 11, 10, 13, 12, 14, 15, 13, 16, 17 };
+ fp_t const cp[] = { 11, 13, 12, 15, 14, 16, 17, 15, 18, 19 };
+
+ fp_t const * const x[] = { o, h, l, cp };
+
+ /* k=3, offset=2, y_n=8 */
+ /* HH3 starting at index 2: max(h[0..2])=14, max(h[1..3])=14, max(h[2..4])=16, max(h[3..5])=16, max(h[4..6])=17, max(h[5..7])=18, max(h[6..8])=18, max(h[7..9])=19 */
+ /* LL3 starting at index 2: min(l[0..2])=10, min(l[1..3])=10, min(l[2..4])=12, min(l[3..5])=12, min(l[4..6])=14, min(l[5..7])=14, min(l[6..8])=13, min(l[7..9])=16 */
+ fp_t const eu[] = { 14, 16, 16, 17, 18, 18, 19, 20 };
+ fp_t const el[] = { 9, 10, 10, 12, 12, 13, 13, 13 };
+ fp_t const * const exp[2] = { eu, el };
+
+ uint_t k = 3;
+ return ts_test(ts_channel, ts_channel_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_channel_p);
+}
diff --git a/test/chebyshev_distance.c b/test/chebyshev_distance.c
new file mode 100644
index 0000000..7bce0ee
--- /dev/null
+++ b/test/chebyshev_distance.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_chebyshev_distance( void )
+{
+ fp_t const x0[] = { 3.000000, -5.000000, 0.000000, 1.000000 };
+ fp_t const x1[] = { 4.000000, 12.000000, 5.000000, -2.000000 };
+ fp_t const e0[] = { 4.000000, 12.000000, 5.000000, 2.000000 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_chebyshev_distance, ts_chebyshev_distance_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_chebyshev_distance_p);
+}
diff --git a/test/chebyshev_t.c b/test/chebyshev_t.c
new file mode 100644
index 0000000..93ed0fb
--- /dev/null
+++ b/test/chebyshev_t.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_chebyshev_t( void )
+{
+ fp_t const x0[] = { 0.000000, 0.500000, 1.000000, 0.700000, 0.000000, 2.000000, 2.000000, 2.000000, 1.500000, -2.000000 };
+ fp_t const x1[] = { 0.000000, 1.000000, 2.000000, 3.000000, 4.000000, 0.000000, 1.000000, 2.000000, 3.000000, 2.000000 };
+ fp_t const e0[] = { 1.000000, 0.500000, 1.000000, -0.728000, 1.000000, 1.000000, 2.000000, 7.000000, 9.000000, 7.000000 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_chebyshev_t, ts_chebyshev_t_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_chebyshev_t_p);
+}
diff --git a/test/chi_squared_pdf.c b/test/chi_squared_pdf.c
new file mode 100644
index 0000000..c2f5cdb
--- /dev/null
+++ b/test/chi_squared_pdf.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_chi_squared_pdf( void )
+{
+ fp_t const c[] = { 3.000000 };
+
+ fp_t const x0[] = { 0.100000, 1.000000, 2.000000, 3.000000, 5.000000 };
+ fp_t const e0[] = { 0.120004, 0.241971, 0.207554, 0.154180, 0.073225 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_chi_squared_pdf, ts_chi_squared_pdf_offset, x, e, sizeof( c ) / sizeof( *c ), (ts_constant_t *)&c, &ts_chi_squared_pdf_p);
+}
diff --git a/test/choppiness_index.c b/test/choppiness_index.c
new file mode 100644
index 0000000..fda6520
--- /dev/null
+++ b/test/choppiness_index.c
@@ -0,0 +1,23 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_choppiness_index( void )
+{
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28 };
+ fp_t const c[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 };
+ fp_t const v[] = { 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ /* k=3, 2k=6, offset=6, H-L=3 for all bars */
+ /* At xi=6: sum_range of bars 0-2 = 9, HH6 of bars 1-6 = 18, LL6 of bars 1-6 = 10, range=8 */
+ /* CI = 100 * ln(9/8) / ln(3/e) = 100 * 0.11778 / 0.04931 = 238.9 */
+ fp_t const e[] = { 10.72107, 10.72107, 10.72107, 10.72107, 10.72107, 10.72107, 10.72107, 10.72107, 10.72107, 10.72107, 10.72107, 10.72107, 10.72107, 10.72107 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_choppiness_index, ts_choppiness_index_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_choppiness_index_p);
+}
diff --git a/test/close_minus_open.c b/test/close_minus_open.c
new file mode 100644
index 0000000..c6030e3
--- /dev/null
+++ b/test/close_minus_open.c
@@ -0,0 +1,17 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_close_minus_open( void )
+{
+ fp_t const x0[] = { 1, 2, 3, 4, 5 };
+ fp_t const x1[] = { 2, 3, 4, 5, 6 };
+ fp_t const x2[] = { 0, 1, 2, 3, 4 };
+ fp_t const x3[] = { 3, 5, 7, 9, 11 };
+ fp_t const x4[] = { 1, 1, 1, 1, 1 };
+ fp_t const e0[] = { 2, 3, 4, 5, 6 };
+
+ fp_t const * const x[] = { x0, x1, x2, x3, x4 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_close_minus_open, ts_close_minus_open_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_close_minus_open_p);
+}
diff --git a/test/close_to_high.c b/test/close_to_high.c
new file mode 100644
index 0000000..295eb92
--- /dev/null
+++ b/test/close_to_high.c
@@ -0,0 +1,17 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_close_to_high( void )
+{
+ fp_t const x0[] = { 1, 1, 1, 1, 1 };
+ fp_t const x1[] = { 4, 6, 8, 10, 12 };
+ fp_t const x2[] = { 0, 2, 4, 6, 8 };
+ fp_t const x3[] = { 2, 4, 6, 8, 10 };
+ fp_t const x4[] = { 1, 1, 1, 1, 1 };
+ fp_t const e0[] = { 0.5, 0.5, 0.5, 0.5, 0.5 };
+
+ fp_t const * const x[] = { x0, x1, x2, x3, x4 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_close_to_high, ts_close_to_high_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_close_to_high_p);
+}
diff --git a/test/close_to_open_ratio.c b/test/close_to_open_ratio.c
new file mode 100644
index 0000000..a636186
--- /dev/null
+++ b/test/close_to_open_ratio.c
@@ -0,0 +1,17 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_close_to_open_ratio( void )
+{
+ fp_t const x0[] = { 2, 4, 6, 8, 10 };
+ fp_t const x1[] = { 3, 5, 7, 9, 11 };
+ fp_t const x2[] = { 1, 3, 5, 7, 9 };
+ fp_t const x3[] = { 4, 8, 12, 16, 20 };
+ fp_t const x4[] = { 1, 1, 1, 1, 1 };
+ fp_t const e0[] = { 2, 2, 2, 2, 2 };
+
+ fp_t const * const x[] = { x0, x1, x2, x3, x4 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_close_to_open_ratio, ts_close_to_open_ratio_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_close_to_open_ratio_p);
+}
diff --git a/test/cmf.c b/test/cmf.c
new file mode 100644
index 0000000..66b4c3f
--- /dev/null
+++ b/test/cmf.c
@@ -0,0 +1,41 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_cmf( void )
+{
+ uint_t k = TS_TEST_K;
+
+ fp_t const o[] = { 10, 11, 12, 11, 12, 10, 11, 13, 12, 14 };
+ fp_t const h[] = { 12, 13, 14, 12, 14, 11, 12, 15, 14, 16 };
+ fp_t const l[] = { 8, 9, 10, 9, 10, 9, 10, 11, 10, 12 };
+ fp_t const c[] = { 11, 12, 10, 11, 11, 10, 12, 14, 13, 15 };
+ fp_t const v[] = { 100, 200, 300, 400, 500, 300, 200, 400, 300, 500 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t mf_vals[10] = { 0 };
+ for( uint_t i = 0; i < 10; ++i )
+ {
+ fp_t hl = h[i] - l[i];
+ mf_vals[i] = hl != 0 ? ( ( c[i] - l[i] ) - ( h[i] - c[i] ) ) / hl * v[i] : 0;
+ }
+
+ fp_t e[6] = { 0 };
+ for( uint_t i = k - 1; i < 10; ++i )
+ {
+ fp_t sum_mf = 0;
+ fp_t sum_v = 0;
+ for( uint_t j = 0; j < k; ++j )
+ {
+ uint_t idx = i - k + 1 + j;
+ sum_mf += mf_vals[idx];
+ sum_v += v[idx];
+ }
+ e[i - k + 1] = sum_v != 0 ? sum_mf / sum_v : 0;
+ }
+ fp_t const * const e_arr[1] = { e };
+
+ uint_t c_val = k;
+
+ return ts_test(ts_cmf, ts_cmf_offset, x, e_arr, 10, (ts_constant_t *)&c_val, &ts_cmf_p);
+}
diff --git a/test/cmo.c b/test/cmo.c
new file mode 100644
index 0000000..00fdb66
--- /dev/null
+++ b/test/cmo.c
@@ -0,0 +1,25 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_cmo( void )
+{
+ /* k=3, prices: 10, 12, 14, 13, 15, 16, 18, 17, 19, 20 */
+ fp_t const x[] = { 10, 12, 14, 13, 15, 16, 18, 17, 19, 20 };
+
+ fp_t const * const inp[1] = { x };
+
+ /* i=1: diff=+2, i=2: diff=+2, i=3: diff=-1 */
+ /* First window (i=1..3): up=2+2+0=4, down=0+0+1=1, CMO=100*(4-1)/(4+1)=60 */
+ /* Slide: add i=4 diff=+2, remove i=1 diff=+2: up=4-2+2=4, down=1-0+0=1, CMO=60 */
+ /* Slide: add i=5 diff=+1, remove i=2 diff=+2: up=4-2+1=3, down=1-0+0=1, CMO=100*(3-1)/(3+1)=50 */
+ /* Slide: add i=6 diff=+2, remove i=3 diff=-1: up=3-0+2=5, down=1-1+0=0, CMO=100*(5-0)/(5+0)=100 */
+ /* Slide: add i=7 diff=-1, remove i=4 diff=+2: up=5-2+0=3, down=0-0+1=1, CMO=100*(3-1)/(3+1)=50 */
+ /* Slide: add i=8 diff=+2, remove i=5 diff=+1: up=3-1+2=4, down=1-0+0=1, CMO=100*(4-1)/(4+1)=60 */
+ /* Slide: add i=9 diff=+1, remove i=6 diff=+2: up=4-2+1=3, down=1-0+0=1, CMO=50 */
+ fp_t const e[] = { 60, 60, 50, 100, 50, 60, 50 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_cmo, ts_cmo_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_cmo_p);
+}
diff --git a/test/coppock_curve.c b/test/coppock_curve.c
new file mode 100644
index 0000000..48239e4
--- /dev/null
+++ b/test/coppock_curve.c
@@ -0,0 +1,26 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_coppock_curve( void )
+{
+ fp_t const x[] = { 100, 105, 98, 110, 102, 115, 108, 120, 106, 112, 118, 99, 113, 107, 121, 114, 109, 116, 103, 111, 119, 104, 117, 108, 122, 110, 115, 107, 121, 105 };
+
+ fp_t const * const inp[1] = { x };
+
+ /*
+ * Coppock = expanding WMA(ROC(14) + ROC(11)), full WMA(10) from idx 9,
+ * then rolling WMA(10). ROC(k) = (x[i] - x[i-k]) / x[i-k] * 100.
+ * WMA weights: most recent value gets highest weight.
+ * Offset = 14, first output at index 14.
+ */
+ fp_t const e[] = {
+ 31.00000000, 27.44537815, 23.27989457, 20.98845532, 17.79356430,
+ 15.48386172, 14.28049689, 12.48191637, 11.72007206, 10.86293252,
+ 6.25323376, 3.24940269, 2.78976008, 0.81656550, 3.89753260,
+ 4.22501503
+ };
+ fp_t const * const exp[1] = { e };
+
+ return ts_test(ts_coppock_curve, ts_coppock_curve_offset, inp, exp, 30, (ts_constant_t *)NULL, &ts_coppock_curve_p);
+}
diff --git a/test/copysign.c b/test/copysign.c
new file mode 100644
index 0000000..cab7a99
--- /dev/null
+++ b/test/copysign.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_copysign( void )
+{
+ fp_t const x0[] = { 1.000000, 2.000000, 3.000000, 4.000000 };
+ fp_t const x1[] = { 1.000000, -1.000000, 1.000000, -1.000000 };
+ fp_t const e0[] = { 1.000000, -2.000000, 3.000000, -4.000000 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_copysign, ts_copysign_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_copysign_p);
+}
diff --git a/test/correlation_coefficient.c b/test/correlation_coefficient.c
new file mode 100644
index 0000000..f1054d0
--- /dev/null
+++ b/test/correlation_coefficient.c
@@ -0,0 +1,16 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_correlation_coefficient( void )
+{
+ fp_t const x0[] = { 1, 2, 3, 4, 5, 6, 7 };
+ fp_t const x1[] = { 2, 4, 6, 8, 10, 12, 14 };
+ fp_t const e0[] = { 1, 1, 1, 1, 1 };
+
+ fp_t const * const x[] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ uint_t k = 3;
+
+ return ts_test(ts_correlation_coefficient, ts_correlation_coefficient_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&k, &ts_correlation_coefficient_p);
+}
diff --git a/test/cosecant.c b/test/cosecant.c
new file mode 100644
index 0000000..1ebe741
--- /dev/null
+++ b/test/cosecant.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_cosecant( void )
+{
+ fp_t const x0[] = { 0.5, 1.0, 1.5, 2.0, 2.5 };
+ fp_t const e0[] = { 2.085830, 1.188395, 1.002511, 1.099750, 1.670922 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_cosecant, ts_cosecant_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_cosecant_p);
+}
diff --git a/test/cosecant_hyperbolic.c b/test/cosecant_hyperbolic.c
new file mode 100644
index 0000000..e6426a3
--- /dev/null
+++ b/test/cosecant_hyperbolic.c
@@ -0,0 +1,9 @@
+#include <stddef.h>
+#include "ts.h"
+bool ts_test_cosecant_hyperbolic( void ) {
+ fp_t const x0[] = { 0.5, 1.0, 1.5, 2.0, 2.5 };
+ fp_t const e0[] = { 1.919035, 0.850918, 0.469642, 0.275721, 0.165284 };
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+ return ts_test(ts_cosecant_hyperbolic, ts_cosecant_hyperbolic_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_cosecant_hyperbolic_p);
+}
diff --git a/test/cosecant_hyperbolic_inverse.c b/test/cosecant_hyperbolic_inverse.c
new file mode 100644
index 0000000..3b4fea9
--- /dev/null
+++ b/test/cosecant_hyperbolic_inverse.c
@@ -0,0 +1,9 @@
+#include <stddef.h>
+#include "ts.h"
+bool ts_test_cosecant_hyperbolic_inverse( void ) {
+ fp_t const x0[] = { 1, 2, 3, 4, 5 };
+ fp_t const e0[] = { 0.881374, 0.481212, 0.327450, 0.247466, 0.198690 };
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+ return ts_test(ts_cosecant_hyperbolic_inverse, ts_cosecant_hyperbolic_inverse_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_cosecant_hyperbolic_inverse_p);
+}
diff --git a/test/cosecant_inverse.c b/test/cosecant_inverse.c
new file mode 100644
index 0000000..02c0a2a
--- /dev/null
+++ b/test/cosecant_inverse.c
@@ -0,0 +1,9 @@
+#include <stddef.h>
+#include "ts.h"
+bool ts_test_cosecant_inverse( void ) {
+ fp_t const x0[] = { 2, 3, 4, 5, 10 };
+ fp_t const e0[] = { 0.523599, 0.339837, 0.252680, 0.201358, 0.100167 };
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+ return ts_test(ts_cosecant_inverse, ts_cosecant_inverse_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_cosecant_inverse_p);
+}
diff --git a/test/cosine.c b/test/cosine.c
new file mode 100644
index 0000000..6dac0cf
--- /dev/null
+++ b/test/cosine.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_cosine( void )
+{
+ fp_t const x0[] = { 0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0 };
+ fp_t const e0[] = { 1.000000, 0.877583, 0.540302, 0.070737, -0.416147, -0.801144, -0.989992, -0.936457, -0.653644 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_cosine, ts_cosine_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_cosine_p);
+}
diff --git a/test/cosine_hyperbolic.c b/test/cosine_hyperbolic.c
new file mode 100644
index 0000000..02cb98a
--- /dev/null
+++ b/test/cosine_hyperbolic.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_cosine_hyperbolic( void )
+{
+ fp_t const x0[] = { 0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0 };
+ fp_t const e0[] = { 1.000000, 1.127626, 1.543081, 2.352410, 3.762196, 6.132289, 10.067662, 16.572825, 27.308233 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_cosine_hyperbolic, ts_cosine_hyperbolic_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_cosine_hyperbolic_p);
+}
diff --git a/test/cosine_inverse.c b/test/cosine_inverse.c
new file mode 100644
index 0000000..8a16df6
--- /dev/null
+++ b/test/cosine_inverse.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_cosine_inverse( void )
+{
+ fp_t const x0[] = { 0, 0.25, 0.5, 0.75, 1.0 };
+ fp_t const e0[] = { 1.570796, 1.318116, 1.047198, 0.722734, 0 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_cosine_inverse, ts_cosine_inverse_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_cosine_inverse_p);
+}
diff --git a/test/cosine_similarity.c b/test/cosine_similarity.c
new file mode 100644
index 0000000..1fb36b9
--- /dev/null
+++ b/test/cosine_similarity.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_cosine_similarity( void )
+{
+ fp_t const x0[] = { 3.000000, 0.000000, 1.000000, -1.000000 };
+ fp_t const x1[] = { 4.000000, 0.000000, 1.000000, 1.000000 };
+ fp_t const e0[] = { 1.000000, 0.000000, 1.000000, -1.000000 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_cosine_similarity, ts_cosine_similarity_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_cosine_similarity_p);
+}
diff --git a/test/cotangent.c b/test/cotangent.c
new file mode 100644
index 0000000..33634c5
--- /dev/null
+++ b/test/cotangent.c
@@ -0,0 +1,9 @@
+#include <stddef.h>
+#include "ts.h"
+bool ts_test_cotangent( void ) {
+ fp_t const x0[] = { 0.5, 1.0, 1.5, 2.0, 2.5 };
+ fp_t const e0[] = { 1.830488, 0.642093, 0.070915, -0.457658, -1.338648 };
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+ return ts_test(ts_cotangent, ts_cotangent_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_cotangent_p);
+}
diff --git a/test/cotangent_hyperbolic.c b/test/cotangent_hyperbolic.c
new file mode 100644
index 0000000..b7963a4
--- /dev/null
+++ b/test/cotangent_hyperbolic.c
@@ -0,0 +1,9 @@
+#include <stddef.h>
+#include "ts.h"
+bool ts_test_cotangent_hyperbolic( void ) {
+ fp_t const x0[] = { 0.5, 1.0, 1.5, 2.0, 2.5 };
+ fp_t const e0[] = { 2.163953, 1.313035, 1.104791, 1.037315, 1.013567 };
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+ return ts_test(ts_cotangent_hyperbolic, ts_cotangent_hyperbolic_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_cotangent_hyperbolic_p);
+}
diff --git a/test/cotangent_hyperbolic_inverse.c b/test/cotangent_hyperbolic_inverse.c
new file mode 100644
index 0000000..6fbddff
--- /dev/null
+++ b/test/cotangent_hyperbolic_inverse.c
@@ -0,0 +1,9 @@
+#include <stddef.h>
+#include "ts.h"
+bool ts_test_cotangent_hyperbolic_inverse( void ) {
+ fp_t const x0[] = { 2, 3, 4, 5, 10 };
+ fp_t const e0[] = { 0.549306, 0.346574, 0.255413, 0.202733, 0.100335 };
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+ return ts_test(ts_cotangent_hyperbolic_inverse, ts_cotangent_hyperbolic_inverse_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_cotangent_hyperbolic_inverse_p);
+}
diff --git a/test/cotangent_inverse.c b/test/cotangent_inverse.c
new file mode 100644
index 0000000..86a1287
--- /dev/null
+++ b/test/cotangent_inverse.c
@@ -0,0 +1,9 @@
+#include <stddef.h>
+#include "ts.h"
+bool ts_test_cotangent_inverse( void ) {
+ fp_t const x0[] = { -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5 };
+ fp_t const e0[] = { 2.944197, 2.896614, 2.819842, 2.677945, 2.356194, 1.570796, 0.785398, 0.463648, 0.321751, 0.244979, 0.197396 };
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+ return ts_test(ts_cotangent_inverse, ts_cotangent_inverse_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_cotangent_inverse_p);
+}
diff --git a/test/crossover.c b/test/crossover.c
new file mode 100644
index 0000000..3aa8f0f
--- /dev/null
+++ b/test/crossover.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_crossover( void )
+{
+ fp_t const x0[] = { 1, 2, 4, 5, 4, 2, 1 };
+ fp_t const x1[] = { 3, 3, 3, 3, 3, 3, 3 };
+ fp_t const e0[] = { 0, 1, 0, 0, -1, 0 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_crossover, ts_crossover_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_crossover_p);
+}
diff --git a/test/cumulative_product.c b/test/cumulative_product.c
new file mode 100644
index 0000000..3ae6708
--- /dev/null
+++ b/test/cumulative_product.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_cumulative_product( void )
+{
+ fp_t const x0[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+ fp_t const e0[] = { 1, 2, 6, 24, 120, 720, 5040, 40320 };
+
+ fp_t const * const x[] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_cumulative_product, ts_cumulative_product_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_cumulative_product_p);
+}
diff --git a/test/cumulative_sum.c b/test/cumulative_sum.c
new file mode 100644
index 0000000..828ef20
--- /dev/null
+++ b/test/cumulative_sum.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_cumulative_sum( void )
+{
+ fp_t const x0[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+ fp_t const e0[] = { 1, 3, 6, 10, 15, 21, 28, 36 };
+
+ fp_t const * const x[] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_cumulative_sum, ts_cumulative_sum_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_cumulative_sum_p);
+}
diff --git a/test/cumulative_volume_delta.c b/test/cumulative_volume_delta.c
new file mode 100644
index 0000000..024b8b5
--- /dev/null
+++ b/test/cumulative_volume_delta.c
@@ -0,0 +1,27 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_cumulative_volume_delta( void )
+{
+ fp_t const o[] = { 10, 11, 12, 11, 12, 10, 11, 13, 12, 14 };
+ fp_t const h[] = { 12, 13, 14, 12, 14, 11, 12, 15, 14, 16 };
+ fp_t const l[] = { 8, 9, 10, 9, 10, 9, 10, 11, 10, 12 };
+ fp_t const c[] = { 11, 12, 10, 11, 11, 10, 12, 14, 13, 15 };
+ fp_t const v[] = { 100, 200, 300, 400, 500, 300, 200, 400, 300, 500 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t e[10] = { 0 };
+ fp_t cum = 0;
+ for( uint_t i = 0; i < 10; ++i )
+ {
+ fp_t diff = c[i] - o[i];
+ fp_t sign = ( diff > 0 ) ? 1.0 : ( diff < 0 ) ? -1.0 : 0.0;
+ cum += v[i] * sign;
+ e[i] = cum;
+ }
+
+ fp_t const * const e_arr[1] = { e };
+
+ return ts_test(ts_cumulative_volume_delta, ts_cumulative_volume_delta_offset, x, e_arr, 10, (ts_constant_t *)NULL, &ts_cumulative_volume_delta_p);
+}
diff --git a/test/cv.c b/test/cv.c
new file mode 100644
index 0000000..2c41604
--- /dev/null
+++ b/test/cv.c
@@ -0,0 +1,25 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_cv( void )
+{
+ fp_t const o[] = { 10, 11, 12, 11, 12, 13, 14, 13, 14, 15, 14 };
+ fp_t const h[] = { 12, 13, 14, 12, 14, 15, 16, 14, 16, 17, 16 };
+ fp_t const l[] = { 8, 9, 10, 9, 10, 11, 12, 11, 12, 13, 12 };
+ fp_t const c[] = { 11, 12, 10, 11, 11, 14, 15, 13, 15, 16, 15 };
+ fp_t const v[] = { 100, 200, 300, 400, 500, 150, 250, 350, 450, 550, 650 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t fast = 3;
+ fp_t slow = 5;
+
+
+ fp_t e[] = { -0.020148, 0.003635, -0.117257, -0.040582, 0.003017, 0.026068 };
+
+ fp_t const * const e_arr[1] = { e };
+
+ fp_t constants[2] = { fast, slow };
+
+ return ts_test(ts_cv, ts_cv_offset, x, e_arr, 11, (ts_constant_t *)constants, &ts_cv_p);
+}
diff --git a/test/darvas_box.c b/test/darvas_box.c
new file mode 100644
index 0000000..af5e857
--- /dev/null
+++ b/test/darvas_box.c
@@ -0,0 +1,23 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_darvas_box( void )
+{
+ fp_t const o[] = { 10, 12, 11, 14, 13, 15, 16, 14, 17, 18 };
+ fp_t const h[] = { 12, 14, 13, 16, 15, 17, 18, 16, 19, 20 };
+ fp_t const l[] = { 9, 11, 10, 13, 12, 14, 15, 13, 16, 17 };
+ fp_t const cp[] = { 11, 13, 12, 15, 14, 16, 17, 15, 18, 19 };
+
+ fp_t const * const x[] = { o, h, l, cp };
+
+ /* k=3, offset=2, y_n=8 */
+ /* HH3 starting at index 2: 14, 14, 16, 16, 17, 18, 18, 19 */
+ /* LL3 starting at index 2: 10, 10, 12, 12, 14, 14, 13, 16 */
+ fp_t const et[] = { 14, 16, 16, 17, 18, 18, 19, 20 };
+ fp_t const eb[] = { 9, 10, 10, 12, 12, 13, 13, 13 };
+ fp_t const * const exp[2] = { et, eb };
+
+ uint_t k = 3;
+ return ts_test(ts_darvas_box, ts_darvas_box_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_darvas_box_p);
+}
diff --git a/test/dawsn.c b/test/dawsn.c
new file mode 100644
index 0000000..2e4e482
--- /dev/null
+++ b/test/dawsn.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_dawsn( void )
+{
+ fp_t const x0[] = { 0.000000, 0.250000, 0.500000, 1.000000, -1.000000, 2.000000 };
+ fp_t const e0[] = { 0.000000, 0.239839, 0.424436, 0.538080, -0.538080, 0.301340 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_dawsn, ts_dawsn_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_dawsn_p);
+}
diff --git a/test/decay_exponential.c b/test/decay_exponential.c
new file mode 100644
index 0000000..363dbed
--- /dev/null
+++ b/test/decay_exponential.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_decay_exponential( void )
+{
+ uint_t const c[] = { 3 };
+
+ fp_t const x0[] = { 5, 3, 4, 2, 6, 1 };
+ fp_t const e0[] = { 2.666666666666667, 6, 4 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_decay_exponential, ts_decay_exponential_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_decay_exponential_p);
+}
diff --git a/test/decay_linear.c b/test/decay_linear.c
new file mode 100644
index 0000000..24d0adf
--- /dev/null
+++ b/test/decay_linear.c
@@ -0,0 +1,14 @@
+#include "ts.h"
+
+bool ts_test_decay_linear( void )
+{
+ uint_t const c[] = { 3 };
+
+ fp_t const x0[] = { 6, 3, 4, 2, 6, 1 };
+ fp_t const e0[] = { 2.66667, 6.0, 4.0 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_decay_linear, ts_decay_linear_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_decay_linear_p);
+}
diff --git a/test/degree_radian.c b/test/degree_radian.c
new file mode 100644
index 0000000..7570121
--- /dev/null
+++ b/test/degree_radian.c
@@ -0,0 +1,14 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_degree_radian( void )
+{
+ fp_t const x[] = { 0, 30, 45, 60, 90, 180, 270, 360 };
+ fp_t const e[] = { 0, PI / 6, PI / 4, PI / 3, HALF_PI, PI, 3 * HALF_PI, 2 * PI };
+
+ fp_t const * const inp[1] = { x };
+ fp_t const * const exp[1] = { e };
+
+ return ts_test(ts_degree_radian, ts_degree_radian_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)NULL, &ts_degree_radian_p);
+}
diff --git a/test/delta.c b/test/delta.c
new file mode 100644
index 0000000..d4856a6
--- /dev/null
+++ b/test/delta.c
@@ -0,0 +1,16 @@
+#include <stddef.h>
+#include <stdio.h>
+#include <tgmath.h>
+#include "ts.h"
+
+bool ts_test_delta( void )
+{
+ fp_t x0[] = { -4, -3, -2, -1, 0, 1, 2, 3, 4 };
+ fp_t e0[] = { 3, 3, 3, 3, 3, 3 };
+
+ fp_t const * const x[] = { x0 };
+ fp_t const * const e[] = { e0 };
+ uint_t const c[] = { 3 };
+
+ return ts_test(ts_delta, ts_delta_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)c, &ts_delta_p);
+}
diff --git a/test/deviation_average.c b/test/deviation_average.c
new file mode 100644
index 0000000..29bdab3
--- /dev/null
+++ b/test/deviation_average.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_deviation_average( void )
+{
+ uint_t const c[] = { 3 };
+
+ fp_t const x0[] = { 1, 2, 3, 4, 5 };
+ fp_t const e0[] = { 0.666667, 0.666667, 0.666667 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_deviation_average, ts_deviation_average_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_deviation_average_p);
+}
diff --git a/test/deviation_standard.c b/test/deviation_standard.c
new file mode 100644
index 0000000..e4cedb2
--- /dev/null
+++ b/test/deviation_standard.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_deviation_standard( void )
+{
+ uint_t const c[] = { 3 };
+
+ fp_t const x0[] = { 1, 2, 3, 4, 5 };
+ fp_t const e0[] = { 0.816497, 0.816497, 0.816497 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_deviation_standard, ts_deviation_standard_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_deviation_standard_p);
+}
diff --git a/test/deviation_standard_average_exponential.c b/test/deviation_standard_average_exponential.c
new file mode 100644
index 0000000..b5aa625
--- /dev/null
+++ b/test/deviation_standard_average_exponential.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_deviation_standard_average_exponential( void )
+{
+ fp_t const c[] = { 0.5 };
+
+ fp_t const x0[] = { 1, 2, 3, 4, 5 };
+ fp_t const e0[] = { 1.000000, 0.707107, 1.172604, 1.489547, 1.693277 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_deviation_standard_average_exponential, ts_deviation_standard_average_exponential_offset, x, e, sizeof( c ) / sizeof( *c ), (ts_constant_t *)&c, &ts_deviation_standard_average_exponential_p);
+}
diff --git a/test/deviation_vector.c b/test/deviation_vector.c
new file mode 100644
index 0000000..8e4e2c6
--- /dev/null
+++ b/test/deviation_vector.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_deviation_vector( void )
+{
+ uint_t const c[] = { 3 };
+
+ fp_t const x0[] = { 1, 2, 3, 4, 5 };
+ fp_t const e0[] = { 0.816497, 0.816497, 0.816497 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_deviation_vector, ts_deviation_vector_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_deviation_vector_p);
+}
diff --git a/test/di_minus.c b/test/di_minus.c
new file mode 100644
index 0000000..35d2545
--- /dev/null
+++ b/test/di_minus.c
@@ -0,0 +1,20 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_di_minus( void )
+{
+ fp_t const o[] = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
+ fp_t const h[] = { 12, 11, 10, 9, 8, 7, 6, 5, 4, 3 };
+ fp_t const l[] = { 8, 6, 4, 2, 0, -2, -4, -6, -8,-10 };
+ fp_t const c[] = { 11, 10, 9, 8, 7, 6, 5, 4, 3, 2 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ /* k=3, -DM=2 each period, +DM=0 => -DI = 100*EMA(-DM)/(EMA(-DM)+EMA(+DM)) */
+ fp_t const e[] = { 100., 100., 100., 100., 100., 100., 100. };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_di_minus, ts_di_minus_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_di_minus_p);
+}
diff --git a/test/di_plus.c b/test/di_plus.c
new file mode 100644
index 0000000..f8667fb
--- /dev/null
+++ b/test/di_plus.c
@@ -0,0 +1,20 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_di_plus( void )
+{
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
+ fp_t const c[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ /* k=3, +DM=1 (H increases by 1 each bar), -DM=0, +DI = 100*EMA(+DM)/(EMA(+DM)+EMA(-DM)) = 100 */
+ fp_t const e[] = { 100, 100, 100, 100, 100, 100, 100 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_di_plus, ts_di_plus_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_di_plus_p);
+}
diff --git a/test/digamma.c b/test/digamma.c
new file mode 100644
index 0000000..c9e221c
--- /dev/null
+++ b/test/digamma.c
@@ -0,0 +1,11 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_digamma( void )
+{
+ fp_t const x0[] = { 10.000000, 15.000000, 20.000000, 50.000000, 100.000000 };
+ fp_t const e0[] = { 2.2517525891, 2.6743466617, 2.9705239922, 3.9019896734, 4.6001618527 };
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+ return ts_test(ts_digamma, ts_digamma_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_digamma_p);
+}
diff --git a/test/disparity_index.c b/test/disparity_index.c
new file mode 100644
index 0000000..1860f8f
--- /dev/null
+++ b/test/disparity_index.c
@@ -0,0 +1,27 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_disparity_index( void )
+{
+ fp_t const x0[] = { 10, 10, 10, 10, 15, 15, 15 };
+ fp_t const * const x[] = { x0 };
+
+ uint_t k = 3;
+
+ fp_t sma = (10 + 10 + 10) / 3.0;
+ fp_t e[5] = { 0 };
+ e[0] = (10 - sma) / sma * 100;
+ sma = sma + (10 - 10) / 3.0;
+ e[1] = (10 - sma) / sma * 100;
+ sma = sma + (15 - 10) / 3.0;
+ e[2] = (15 - sma) / sma * 100;
+ sma = sma + (15 - 10) / 3.0;
+ e[3] = (15 - sma) / sma * 100;
+ sma = sma + (15 - 10) / 3.0;
+ e[4] = (15 - sma) / sma * 100;
+
+ fp_t const * const exp[1] = { e };
+
+ return ts_test(ts_disparity_index, ts_disparity_index_offset, x, exp, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&k, &ts_disparity_index_p);
+}
diff --git a/test/distance.c b/test/distance.c
new file mode 100644
index 0000000..c8466b7
--- /dev/null
+++ b/test/distance.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_distance( void )
+{
+ fp_t const x0[] = { 3.000000, 5.000000, 0.000000, 1.000000 };
+ fp_t const x1[] = { 4.000000, 12.000000, 5.000000, 0.000000 };
+ fp_t const e0[] = { 5.000000, 13.000000, 5.000000, 1.000000 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_distance, ts_distance_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_distance_p);
+}
diff --git a/test/division.c b/test/division.c
new file mode 100644
index 0000000..7442b53
--- /dev/null
+++ b/test/division.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include <math.h>
+#include "ts.h"
+
+bool ts_test_division( void )
+{
+ fp_t const x0[] = { 10, 8, 6, 4 };
+ fp_t const x1[] = { 2, 4, 3, 1 };
+ fp_t const e0[] = { 5, 2, 2, 4 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_division, ts_division_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_division_p);
+}
diff --git a/test/donchian_channel.c b/test/donchian_channel.c
new file mode 100644
index 0000000..4ada2d0
--- /dev/null
+++ b/test/donchian_channel.c
@@ -0,0 +1,26 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_donchian_channel( void )
+{
+ fp_t const o[] = { 10, 12, 11, 14, 13, 15, 16, 14, 17, 18 };
+ fp_t const h[] = { 12, 14, 13, 16, 15, 17, 18, 16, 19, 20 };
+ fp_t const l[] = { 9, 11, 10, 13, 12, 14, 15, 13, 16, 17 };
+ fp_t const c[] = { 11, 13, 12, 15, 14, 16, 17, 15, 18, 19 };
+ fp_t const v[] = { 100, 200, 150, 180, 160, 190, 210, 170, 200, 220 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ /* k=3, offset=2, y_n=8 */
+ /* HH3 at index 2..9: 14, 14, 16, 16, 17, 18, 18, 19 */
+ /* LL3 at index 2..9: 10, 10, 12, 12, 14, 14, 13, 16 */
+ /* Mid: 12, 12, 14, 14, 15.5, 16, 15.5, 17.5 */
+ fp_t const eu[] = { 14, 16, 16, 17, 18, 18, 19, 20 };
+ fp_t const el[] = { 9, 10, 10, 12, 12, 13, 13, 13 };
+ fp_t const em[] = { 11.5, 13.0, 13.0, 14.5, 15.0, 15.5, 16.0, 16.5 };
+ fp_t const * const exp[3] = { eu, el, em };
+
+ uint_t k = 3;
+ return ts_test(ts_donchian_channel, ts_donchian_channel_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_donchian_channel_p);
+}
diff --git a/test/donchian_width.c b/test/donchian_width.c
new file mode 100644
index 0000000..ead859f
--- /dev/null
+++ b/test/donchian_width.c
@@ -0,0 +1,21 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_donchian_width( void )
+{
+ fp_t const o[] = { 10, 12, 11, 14, 13, 15, 16, 14, 17, 18 };
+ fp_t const h[] = { 12, 14, 13, 16, 15, 17, 18, 16, 19, 20 };
+ fp_t const l[] = { 9, 11, 10, 13, 12, 14, 15, 13, 16, 17 };
+ fp_t const c[] = { 11, 13, 12, 15, 14, 16, 17, 15, 18, 19 };
+ fp_t const v[] = { 100, 200, 150, 180, 160, 190, 210, 170, 200, 220 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ /* Width = HH3 - LL3 at index 2..9: 4, 4, 4, 4, 3, 4, 5, 3 */
+ fp_t const e[] = { 5, 6, 6, 5, 6, 5, 6, 7 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_donchian_width, ts_donchian_width_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_donchian_width_p);
+}
diff --git a/test/dpo.c b/test/dpo.c
new file mode 100644
index 0000000..e6e4560
--- /dev/null
+++ b/test/dpo.c
@@ -0,0 +1,24 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_dpo( void )
+{
+ fp_t const x[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+
+ fp_t const * const inp[1] = { x };
+
+ /* k=4, offset=3 */
+ /* SMA4: 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5 */
+ /* DPO[3] = close[3] - SMA4[0] = 4 - 2.5 = 1.5 */
+ /* DPO[4] = close[4] - SMA4[1] = 5 - 3.5 = 1.5 */
+ /* ... all 1.5 */
+
+ fp_t const e[] = { 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5 };
+
+ fp_t const * const exp[1] = { e };
+
+ uint_t c_val = 4;
+
+ return ts_test(ts_dpo, ts_dpo_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&c_val, &ts_dpo_p);
+}
diff --git a/test/dx.c b/test/dx.c
new file mode 100644
index 0000000..f5ee94c
--- /dev/null
+++ b/test/dx.c
@@ -0,0 +1,22 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_dx( void )
+{
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
+ fp_t const c[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ /* k=3, all bars identical pattern */
+ /* +DM=2, -DM=0, TR=3, EMA values stable at 2, 0, 3 */
+ /* +DI=66.667, -DI=0, DX=100 */
+ fp_t const e[] = { 100, 100, 100, 100, 100, 100, 100 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_dx, ts_dx_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_dx_p);
+}
diff --git a/test/ease_of_movement.c b/test/ease_of_movement.c
new file mode 100644
index 0000000..fbe2e35
--- /dev/null
+++ b/test/ease_of_movement.c
@@ -0,0 +1,43 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_ease_of_movement( void )
+{
+ uint_t k = 3;
+
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 };
+ fp_t const c[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+ fp_t const v[] = { 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t eom[10] = { 0 };
+ for( uint_t i = 1; i < 10; ++i )
+ {
+ fp_t hl = h[i] - l[i];
+ if( hl == 0 ) continue;
+ fp_t br = v[i] / hl;
+ if( br == 0 ) continue;
+ fp_t cur_mid = ( h[i] + l[i] ) / 2.0;
+ fp_t prev_mid = ( h[i - 1] + l[i - 1] ) / 2.0;
+ eom[i] = ( cur_mid - prev_mid ) / br;
+ }
+
+ fp_t e[7] = { 0 };
+ {
+ fp_t sum = eom[1] + eom[2] + eom[3];
+ e[0] = sum / (fp_t)k;
+ for( uint_t i = k + 1; i < 10; ++i )
+ {
+ sum = sum - eom[i - k] + eom[i];
+ e[i - k] = sum / (fp_t)k;
+ }
+ }
+
+ fp_t const * const e_arr[1] = { e };
+ uint_t c_val = k;
+
+ return ts_test(ts_ease_of_movement, ts_ease_of_movement_offset, x, e_arr, 10, (ts_constant_t *)&c_val, &ts_ease_of_movement_p);
+}
diff --git a/test/edge_cases.c b/test/edge_cases.c
new file mode 100644
index 0000000..ce47f72
--- /dev/null
+++ b/test/edge_cases.c
@@ -0,0 +1,324 @@
+#include <math.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_edge_cases( void )
+{
+ bool result = true;
+
+ /* === NULL input === */
+
+ /* average with NULL input should return -1 */
+ {
+ uint_t k = 3;
+ int ret = ts_average(NULL, 0, NULL, 0, 5, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* rsi with NULL input should return -1 */
+ {
+ uint_t k = 3;
+ int ret = ts_rsi(NULL, 0, NULL, 0, 5, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* === n = 0 (empty input) === */
+
+ /* average with n=0 should return early, no crash */
+ {
+ uint_t k = 3;
+ int ret = ts_average(NULL, 0, NULL, 0, 0, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* rsi with n=0 should not crash */
+ {
+ uint_t k = 3;
+ int ret = ts_rsi(NULL, 0, NULL, 0, 0, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* deviation_standard with n=0 should return early */
+ {
+ uint_t k = 3;
+ int ret = ts_deviation_standard(NULL, 0, NULL, 0, 0, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* variance with n=0 should return early */
+ {
+ uint_t k = 3;
+ int ret = ts_variance(NULL, 0, NULL, 0, 0, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* === n = 1 (single element) === */
+
+ /* average with n=1, k=1 should produce the element itself */
+ {
+ fp_t const x[] = {5};
+ fp_t const e[] = {5};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 1;
+ result &= ts_test(ts_average, ts_average_offset, inp, exp, 1, (ts_constant_t *)&k, &ts_average_p);
+ }
+
+ /* deviation_standard with n=1, k=1 should produce 0 */
+ {
+ fp_t const x[] = {5};
+ fp_t const e[] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 1;
+ result &= ts_test(ts_deviation_standard, ts_deviation_standard_offset, inp, exp, 1, (ts_constant_t *)&k, &ts_deviation_standard_p);
+ }
+
+ /* variance with n=1, k=1 should produce 0 */
+ {
+ fp_t const x[] = {5};
+ fp_t const e[] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 1;
+ result &= ts_test(ts_variance, ts_variance_offset, inp, exp, 1, (ts_constant_t *)&k, &ts_variance_p);
+ }
+
+ /* === k = 0 (zero period) === */
+
+ /* average with k=0 should return early */
+ {
+ fp_t x[] = {1, 2, 3, 4, 5};
+ fp_t y[5] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t * const out[1] = {y};
+ uint_t k = 0;
+ int ret = ts_average(out, 1, inp, 1, 5, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* rsi with k=0 should return early */
+ {
+ fp_t x[] = {1, 2, 3, 4, 5};
+ fp_t y[5] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t * const out[1] = {y};
+ uint_t k = 0;
+ int ret = ts_rsi(out, 1, inp, 1, 5, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* deviation_standard with k=0 should return early */
+ {
+ fp_t x[] = {1, 2, 3, 4, 5};
+ fp_t y[5] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t * const out[1] = {y};
+ uint_t k = 0;
+ int ret = ts_deviation_standard(out, 1, inp, 1, 5, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* variance with k=0 should return early */
+ {
+ fp_t x[] = {1, 2, 3, 4, 5};
+ fp_t y[5] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t * const out[1] = {y};
+ uint_t k = 0;
+ int ret = ts_variance(out, 1, inp, 1, 5, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* === k > n (period exceeds data) === */
+
+ /* average with k > n should return early */
+ {
+ fp_t x[] = {1, 2, 3};
+ fp_t y[3] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t * const out[1] = {y};
+ uint_t k = 10;
+ int ret = ts_average(out, 1, inp, 1, 3, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* deviation_standard with k > n should return early */
+ {
+ fp_t x[] = {1, 2, 3};
+ fp_t y[3] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t * const out[1] = {y};
+ uint_t k = 10;
+ int ret = ts_deviation_standard(out, 1, inp, 1, 3, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* variance with k > n should return early */
+ {
+ fp_t x[] = {1, 2, 3};
+ fp_t y[3] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t * const out[1] = {y};
+ uint_t k = 10;
+ int ret = ts_variance(out, 1, inp, 1, 3, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* === All-zero input === */
+
+ /* average with all-zero input should produce zeros */
+ {
+ fp_t const x[] = {0, 0, 0, 0, 0};
+ fp_t const e[] = {0, 0, 0};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 3;
+ result &= ts_test(ts_average, ts_average_offset, inp, exp, 5, (ts_constant_t *)&k, &ts_average_p);
+ }
+
+ /* rsi with all-zero input should not crash (all gains/losses are 0) */
+ {
+ fp_t const x[] = {0, 0, 0, 0, 0, 0, 0};
+ fp_t const e[] = {100, 100, 100, 100, 100};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 2;
+ result &= ts_test(ts_rsi, ts_rsi_offset, inp, exp, 7, (ts_constant_t *)&k, &ts_rsi_p);
+ }
+
+ /* deviation_standard with all-zero input should produce 0 */
+ {
+ fp_t const x[] = {0, 0, 0, 0, 0};
+ fp_t const e[] = {0, 0, 0};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 3;
+ result &= ts_test(ts_deviation_standard, ts_deviation_standard_offset, inp, exp, 5, (ts_constant_t *)&k, &ts_deviation_standard_p);
+ }
+
+ /* variance with all-zero input should produce 0 */
+ {
+ fp_t const x[] = {0, 0, 0, 0, 0};
+ fp_t const e[] = {0, 0, 0};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 3;
+ result &= ts_test(ts_variance, ts_variance_offset, inp, exp, 5, (ts_constant_t *)&k, &ts_variance_p);
+ }
+
+ /* === All-same input (zero variance) === */
+
+ /* average with all-same input should produce that value */
+ {
+ fp_t const x[] = {7, 7, 7, 7, 7};
+ fp_t const e[] = {7, 7, 7};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 3;
+ result &= ts_test(ts_average, ts_average_offset, inp, exp, 5, (ts_constant_t *)&k, &ts_average_p);
+ }
+
+ /* rsi with all-same input should produce 100 (no losses) */
+ {
+ fp_t const x[] = {5, 5, 5, 5, 5, 5, 5};
+ fp_t const e[] = {100, 100, 100, 100, 100};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 2;
+ result &= ts_test(ts_rsi, ts_rsi_offset, inp, exp, 7, (ts_constant_t *)&k, &ts_rsi_p);
+ }
+
+ /* deviation_standard with all-same input should produce 0 */
+ {
+ fp_t const x[] = {3, 3, 3, 3, 3};
+ fp_t const e[] = {0, 0, 0};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 3;
+ result &= ts_test(ts_deviation_standard, ts_deviation_standard_offset, inp, exp, 5, (ts_constant_t *)&k, &ts_deviation_standard_p);
+ }
+
+ /* variance with all-same input should produce 0 */
+ {
+ fp_t const x[] = {3, 3, 3, 3, 3};
+ fp_t const e[] = {0, 0, 0};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 3;
+ result &= ts_test(ts_variance, ts_variance_offset, inp, exp, 5, (ts_constant_t *)&k, &ts_variance_p);
+ }
+
+ /* === n == k boundary === */
+
+ /* average with n==k should succeed (produces 1 output) */
+ {
+ fp_t const x[] = {1, 2, 3};
+ fp_t const e[] = {2};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 3;
+ result &= ts_test(ts_average, ts_average_offset, inp, exp, 3, (ts_constant_t *)&k, &ts_average_p);
+ }
+
+ /* rsi with n==k should fail (requires n > k) */
+ {
+ fp_t x[] = {1, 2, 3};
+ fp_t y[3] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t * const out[1] = {y};
+ uint_t k = 3;
+ int ret = ts_rsi(out, 1, inp, 1, 3, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* === NaN input === */
+
+ /* average with NaN input should return -1 (invalid input) */
+ {
+ fp_t x[] = {NAN, 1.0, 2.0, 3.0, 4.0};
+ fp_t y[5] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t * const out[1] = {y};
+ uint_t k = 3;
+ int ret = ts_average(out, 1, inp, 1, 5, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* === Monotonic input (RSI edge cases) === */
+
+ /* rsi with strictly ascending input should produce 100 */
+ {
+ fp_t const x[] = {1, 2, 3, 4, 5, 6, 7};
+ fp_t const e[] = {100, 100, 100, 100, 100};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 2;
+ result &= ts_test(ts_rsi, ts_rsi_offset, inp, exp, 7, (ts_constant_t *)&k, &ts_rsi_p);
+ }
+
+ /* rsi with strictly descending input should produce 0 */
+ {
+ fp_t const x[] = {7, 6, 5, 4, 3, 2, 1};
+ fp_t const e[] = {0, 0, 0, 0, 0};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 2;
+ result &= ts_test(ts_rsi, ts_rsi_offset, inp, exp, 7, (ts_constant_t *)&k, &ts_rsi_p);
+ }
+
+ /* === rsi with k > n === */
+
+ {
+ fp_t x[] = {1, 2, 3};
+ fp_t y[3] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t * const out[1] = {y};
+ uint_t k = 10;
+ int ret = ts_rsi(out, 1, inp, 1, 3, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ return result;
+}
diff --git a/test/elder_force_index.c b/test/elder_force_index.c
new file mode 100644
index 0000000..7144c68
--- /dev/null
+++ b/test/elder_force_index.c
@@ -0,0 +1,32 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_elder_force_index( void )
+{
+ uint_t k = 3;
+
+ fp_t const o[] = { 10, 11, 12, 11, 12, 10, 11, 13, 12, 14 };
+ fp_t const h[] = { 12, 13, 14, 12, 14, 11, 12, 15, 14, 16 };
+ fp_t const l[] = { 8, 9, 10, 9, 10, 9, 10, 11, 10, 12 };
+ fp_t const c[] = { 11, 12, 10, 11, 11, 10, 12, 14, 13, 15 };
+ fp_t const v[] = { 100, 200, 300, 400, 500, 300, 200, 400, 300, 500 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t e[9] = {
+ 200.0,
+ -66.666666666666629,
+ 88.888888888888914,
+ 59.259259259259281,
+ -60.493827160493808,
+ 93.004115226337447,
+ 328.669410150891622,
+ 119.112940100594415,
+ 412.741960067062905
+ };
+
+ fp_t const * const e_arr[1] = { e };
+ uint_t c_val = k;
+
+ return ts_test(ts_elder_force_index, ts_elder_force_index_offset, x, e_arr, 10, (ts_constant_t *)&c_val, &ts_elder_force_index_p);
+}
diff --git a/test/elder_impulse.c b/test/elder_impulse.c
new file mode 100644
index 0000000..95e2c28
--- /dev/null
+++ b/test/elder_impulse.c
@@ -0,0 +1,22 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_elder_impulse( void )
+{
+ /* k=3, offset=5 */
+ fp_t const x[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+
+ fp_t const * const inp[1] = { x };
+
+ /* k=3, offset=4, y_n=6 */
+ /* SMA(3): 11, 12, 13, 14, 15, 16, 17, 18 */
+ /* Osc: 1, 1, 1, 1, 1, 1, 1, 1 */
+ /* Signal (offset=4, 6 values): 1, 1, 1, 1, 1, 1 */
+ fp_t const eos[] = { 1, 1, 1, 1, 1, 1 };
+ fp_t const es[] = { 1, 1, 1, 1, 1, 1 };
+ fp_t const * const exp[2] = { eos, es };
+
+ uint_t k = 3;
+ return ts_test(ts_elder_impulse, ts_elder_impulse_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_elder_impulse_p);
+}
diff --git a/test/elder_ray.c b/test/elder_ray.c
new file mode 100644
index 0000000..315b0fd
--- /dev/null
+++ b/test/elder_ray.c
@@ -0,0 +1,24 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_elder_ray( void )
+{
+ /* k=2, EMA alpha=0.5 */
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
+ fp_t const c[] = { 10, 12, 14, 13, 15, 16, 18, 17, 19, 20 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ /* EMA(2): 10, 11, 12.5, 12.75, 13.875, 14.9375, 16.4688, 16.7344, 17.8672, 18.9336 */
+ /* Bull: 12-10=2, 13-11=2, 14-12.5=1.5, 15-12.75=2.25, 16-13.875=2.125, 17-14.9375=2.0625, 18-16.4688=1.5312, 19-16.7344=2.2656, 20-17.8672=2.1328, 21-18.9336=2.0664 */
+ /* Bear: 9-10=-1, 10-11=-1, 11-12.5=-1.5, 12-12.75=-0.75, 13-13.875=-0.875, 14-14.9375=-0.9375, 15-16.4688=-1.4688, 16-16.7344=-0.7344, 17-17.8672=-0.8672, 18-18.9336=-0.9336 */
+ fp_t const eb[] = { 2, 2, 1.5, 2.25, 2.125, 2.0625, 1.53125, 2.265625, 2.1328125, 2.06640625 };
+ fp_t const ee[] = { -1, -1, -1.5, -0.75, -0.875, -0.9375, -1.46875, -0.734375, -0.8671875, -0.93359375 };
+ fp_t const * const exp[2] = { eb, ee };
+
+ fp_t k = 2;
+ return ts_test(ts_elder_ray, ts_elder_ray_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_elder_ray_p);
+}
diff --git a/test/ellint_1.c b/test/ellint_1.c
new file mode 100644
index 0000000..50bd242
--- /dev/null
+++ b/test/ellint_1.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_ellint_1( void )
+{
+ fp_t const x0[] = { 0.000000, 0.250000, 0.500000, 0.750000, 0.900000 };
+ fp_t const e0[] = { 1.570796, 1.685750, 1.854075, 2.156516, 2.578092 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_ellint_1, ts_ellint_1_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_ellint_1_p);
+}
diff --git a/test/ellint_2.c b/test/ellint_2.c
new file mode 100644
index 0000000..9036741
--- /dev/null
+++ b/test/ellint_2.c
@@ -0,0 +1,11 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_ellint_2( void )
+{
+ fp_t const x0[] = { 0.000000, 0.250000, 0.500000, 0.750000, 0.900000 };
+ fp_t const e0[] = { 1.570796, 1.681966, 1.834119, 2.088133, 2.421504 };
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+ return ts_test(ts_ellint_2, ts_ellint_2_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_ellint_2_p);
+}
diff --git a/test/ema_variable.c b/test/ema_variable.c
new file mode 100644
index 0000000..52fa74c
--- /dev/null
+++ b/test/ema_variable.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_ema_variable( void )
+{
+ fp_t const x0[] = { -10, -7.5, -5, -2.5, 0, 2.5, 5, 7.5, 10 };
+ fp_t const e0[] = { -10.000000, -8.333333, -6.111111, -3.703704, -1.234568, 1.255144, 3.751715, 6.250572, 8.750191 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ fp_t c = 2;
+
+ return ts_test(ts_ema_variable, ts_ema_variable_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_ema_variable_p);
+}
diff --git a/test/envelope.c b/test/envelope.c
new file mode 100644
index 0000000..26d0edb
--- /dev/null
+++ b/test/envelope.c
@@ -0,0 +1,19 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_envelope( void )
+{
+ fp_t const x[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const * const inp[1] = { x };
+
+ /* k=3, pct=0.05, SMA: 11, 12, 13, 14, 15, 16, 17, 18 */
+ /* Upper = SMA*1.05, Lower = SMA*0.95 */
+ fp_t const eu[] = { 11.55, 12.60, 13.65, 14.70, 15.75, 16.80, 17.85, 18.90 };
+ fp_t const el[] = { 10.45, 11.40, 12.35, 13.30, 14.25, 15.20, 16.15, 17.10 };
+ fp_t const * const exp[2] = { eu, el };
+
+ typedef struct { uint_t k; fp_t pct; } c_t;
+ c_t c = { .k = 3, .pct = 0.05 };
+ return ts_test(ts_envelope, ts_envelope_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&c, &ts_envelope_p);
+}
diff --git a/test/erf.c b/test/erf.c
new file mode 100644
index 0000000..dd28c62
--- /dev/null
+++ b/test/erf.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_erf( void )
+{
+ fp_t const x0[] = { -2.000000, -1.000000, 0.000000, 1.000000, 2.000000 };
+ fp_t const e0[] = { -0.995322, -0.842701, 0.000000, 0.842701, 0.995322 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_erf, ts_erf_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_erf_p);
+}
diff --git a/test/erfc.c b/test/erfc.c
new file mode 100644
index 0000000..668912e
--- /dev/null
+++ b/test/erfc.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_erfc( void )
+{
+ fp_t const x0[] = { -2.000000, -1.000000, 0.000000, 1.000000, 2.000000 };
+ fp_t const e0[] = { 1.995322, 1.842701, 1.000000, 0.157299, 0.004678 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_erfc, ts_erfc_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_erfc_p);
+}
diff --git a/test/euclidean_norm.c b/test/euclidean_norm.c
new file mode 100644
index 0000000..475778b
--- /dev/null
+++ b/test/euclidean_norm.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_euclidean_norm( void )
+{
+ fp_t const x0[] = { 3.000000, 5.000000, 0.000000, 1.000000 };
+ fp_t const x1[] = { 4.000000, 12.000000, 5.000000, 0.000000 };
+ fp_t const e0[] = { 5.000000, 13.000000, 5.000000, 1.000000 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_euclidean_norm, ts_euclidean_norm_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_euclidean_norm_p);
+}
diff --git a/test/exp10.c b/test/exp10.c
new file mode 100644
index 0000000..b986269
--- /dev/null
+++ b/test/exp10.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_exp10( void )
+{
+ fp_t const x0[] = { 0.000000, 1.000000, 2.000000, -1.000000, 3.000000 };
+ fp_t const e0[] = { 1.000000, 10.000000, 100.000000, 0.100000, 1000.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_exp10, ts_exp10_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_exp10_p);
+}
diff --git a/test/exp10m1.c b/test/exp10m1.c
new file mode 100644
index 0000000..e0f7988
--- /dev/null
+++ b/test/exp10m1.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_exp10m1( void )
+{
+ fp_t const x0[] = { 0.000000, 1.000000, 2.000000, -1.000000 };
+ fp_t const e0[] = { 0.000000, 9.000000, 99.000000, -0.900000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_exp10m1, ts_exp10m1_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_exp10m1_p);
+}
diff --git a/test/exp2.c b/test/exp2.c
new file mode 100644
index 0000000..bc4df6a
--- /dev/null
+++ b/test/exp2.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_exp2( void )
+{
+ fp_t const x0[] = { 0.000000, 1.000000, 2.000000, 3.000000, 4.000000 };
+ fp_t const e0[] = { 1.000000, 2.000000, 4.000000, 8.000000, 16.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_exp2, ts_exp2_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_exp2_p);
+}
diff --git a/test/exp2m1.c b/test/exp2m1.c
new file mode 100644
index 0000000..530b334
--- /dev/null
+++ b/test/exp2m1.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_exp2m1( void )
+{
+ fp_t const x0[] = { 0.000000, 1.000000, 2.000000, -1.000000 };
+ fp_t const e0[] = { 0.000000, 1.000000, 3.000000, -0.500000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_exp2m1, ts_exp2m1_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_exp2m1_p);
+}
diff --git a/test/expit.c b/test/expit.c
new file mode 100644
index 0000000..cfba0d7
--- /dev/null
+++ b/test/expit.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_expit( void )
+{
+ fp_t const x0[] = { -2.000000, -1.000000, 0.000000, 1.000000, 2.000000 };
+ fp_t const e0[] = { 0.119203, 0.268941, 0.500000, 0.731059, 0.880797 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_expit, ts_expit_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_expit_p);
+}
diff --git a/test/expm1.c b/test/expm1.c
new file mode 100644
index 0000000..0375e98
--- /dev/null
+++ b/test/expm1.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_expm1( void )
+{
+ fp_t const x0[] = { 0.000000, 1.000000, -1.000000, 2.000000, 0.500000 };
+ fp_t const e0[] = { 0.000000, 1.718282, -0.632121, 6.389056, 0.648721 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_expm1, ts_expm1_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_expm1_p);
+}
diff --git a/test/exponential.c b/test/exponential.c
new file mode 100644
index 0000000..ed1f85f
--- /dev/null
+++ b/test/exponential.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_exponential( void )
+{
+ fp_t const c[] = { 2.0 };
+
+ fp_t const x0[] = { 0, 1, 2, 3, 4 };
+ fp_t const e0[] = { 1, 2, 4, 8, 16 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_exponential, ts_exponential_offset, x, e, sizeof( c ) / sizeof( *c ), (ts_constant_t *)&c, &ts_exponential_p);
+}
diff --git a/test/exponential_cdf.c b/test/exponential_cdf.c
new file mode 100644
index 0000000..fd47765
--- /dev/null
+++ b/test/exponential_cdf.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_exponential_cdf( void )
+{
+ fp_t const x0[] = { -1.000000, 0.000000, 0.500000, 1.000000, 2.000000 };
+ fp_t const e0[] = { 0.000000, 0.000000, 0.393469, 0.632121, 0.864665 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_exponential_cdf, ts_exponential_cdf_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_exponential_cdf_p);
+}
diff --git a/test/exponential_natural.c b/test/exponential_natural.c
new file mode 100644
index 0000000..c16df53
--- /dev/null
+++ b/test/exponential_natural.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_exponential_natural( void )
+{
+ fp_t const x0[] = { 0, 0.5, 1, 2, 3 };
+ fp_t const e0[] = { 1, 1.648721, 2.718282, 7.389056, 20.085537 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_exponential_natural, ts_exponential_natural_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_exponential_natural_p);
+}
diff --git a/test/exponential_pdf.c b/test/exponential_pdf.c
new file mode 100644
index 0000000..43b8149
--- /dev/null
+++ b/test/exponential_pdf.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_exponential_pdf( void )
+{
+ fp_t const x0[] = { -1.000000, 0.000000, 0.500000, 1.000000, 2.000000 };
+ fp_t const e0[] = { 0.000000, 1.000000, 0.606531, 0.367879, 0.135335 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_exponential_pdf, ts_exponential_pdf_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_exponential_pdf_p);
+}
diff --git a/test/factorial.c b/test/factorial.c
new file mode 100644
index 0000000..42e8d3d
--- /dev/null
+++ b/test/factorial.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_factorial( void )
+{
+ fp_t const x0[] = { 0.000000, 1.000000, 2.000000, 3.000000, 4.000000, 5.000000, 6.000000, 7.000000, 8.000000, 9.000000, 10.000000 };
+ fp_t const e0[] = { 1.000000, 1.000000, 2.000000, 6.000000, 24.000000, 120.000000, 720.000000, 5040.000000, 40320.000000, 362880.000000, 3628800.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_factorial, ts_factorial_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_factorial_p);
+}
diff --git a/test/factorial_int.c b/test/factorial_int.c
new file mode 100644
index 0000000..0e49cac
--- /dev/null
+++ b/test/factorial_int.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_factorial_int( void )
+{
+ fp_t const x0[] = { 0.000000, 1.000000, 2.000000, 3.000000, 4.000000, 5.000000, 10.000000 };
+ fp_t const e0[] = { 1.000000, 1.000000, 2.000000, 6.000000, 24.000000, 120.000000, 3628800.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_factorial_int, ts_factorial_int_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_factorial_int_p);
+}
diff --git a/test/fdim_2input.c b/test/fdim_2input.c
new file mode 100644
index 0000000..e2b1e0a
--- /dev/null
+++ b/test/fdim_2input.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_fdim_2input( void )
+{
+ fp_t const x0[] = { -3.000000, -1.000000, 0.000000, 1.000000, 3.000000 };
+ fp_t const e0[] = { 0.000000, 0.000000, 0.000000, 1.000000, 3.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_fdim_2input, ts_fdim_2input_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_fdim_2input_p);
+}
diff --git a/test/fibonacci.c b/test/fibonacci.c
new file mode 100644
index 0000000..5a8ab7d
--- /dev/null
+++ b/test/fibonacci.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_fibonacci( void )
+{
+ fp_t const x0[] = { 0.000000, 1.000000, 2.000000, 3.000000, 4.000000, 5.000000, 6.000000, 7.000000, 8.000000, 10.000000 };
+ fp_t const e0[] = { 0.000000, 1.000000, 1.000000, 2.000000, 3.000000, 5.000000, 8.000000, 13.000000, 21.000000, 55.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_fibonacci, ts_fibonacci_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_fibonacci_p);
+}
diff --git a/test/fill_indicator.c b/test/fill_indicator.c
new file mode 100644
index 0000000..071c245
--- /dev/null
+++ b/test/fill_indicator.c
@@ -0,0 +1,40 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_fill_indicator( void )
+{
+ uint_t k = 3;
+
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 8, 10, 10, 11, 12, 13, 14, 15, 16, 17 };
+ fp_t const c[] = { 11, 12, 10, 14, 15, 16, 14, 18, 17, 20 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ /* fill values:
+ * i=0: |11-10|/(12-8)*100 = 1/4*100 = 25
+ * i=1: |12-11|/(13-10)*100 = 1/3*100 = 33.333
+ * i=2: |10-12|/(14-10)*100 = 2/4*100 = 50
+ * i=3: |14-13|/(15-11)*100 = 1/4*100 = 25
+ * i=4: |15-14|/(16-12)*100 = 1/4*100 = 25
+ * i=5: |16-15|/(17-13)*100 = 1/4*100 = 25
+ * i=6: |14-16|/(18-14)*100 = 2/4*100 = 50
+ * i=7: |18-17|/(19-15)*100 = 1/4*100 = 25
+ * i=8: |17-18|/(20-16)*100 = 1/4*100 = 25
+ * i=9: |20-19|/(21-17)*100 = 1/4*100 = 25
+ * SMA(3): i=2: (25+33.333+50)/3 = 36.111
+ * i=3: 36.111 + (25-25)/3 = 36.111
+ * i=4: 36.111 + (25-33.333)/3 = 33.333
+ * i=5: 33.333 + (25-50)/3 = 25
+ * i=6: 25 + (50-25)/3 = 33.333
+ * i=7: 33.333 + (25-25)/3 = 33.333
+ * i=8: 33.333 + (25-25)/3 = 33.333
+ * i=9: 33.333 + (25-50)/3 = 25
+ */
+ fp_t const e[] = { 36.11111, 36.11111, 33.33333, 25, 33.33333, 33.33333, 33.33333, 25 };
+ fp_t const * const exp[1] = { e };
+
+ return ts_test(ts_fill_indicator, ts_fill_indicator_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_fill_indicator_p);
+}
diff --git a/test/fisher_transform.c b/test/fisher_transform.c
new file mode 100644
index 0000000..050a360
--- /dev/null
+++ b/test/fisher_transform.c
@@ -0,0 +1,26 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_fisher_transform( void )
+{
+ /* k=3, prices increasing monotonically */
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
+ fp_t const c[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ /* Window indices [1..3], H=[13,14,15], L=[10,11,12], range=5, val=(14-10)/5=0.8 */
+ /* fisher = 0.5*ln(0.8/0.2) + 0.5*0 = 0.5*ln(4) = 0.69315 */
+ /* Next: H=[14,15,16], L=[11,12,13], val=(15-11)/5=0.8, fisher=0.5*ln(4)+0.5*0.69315=0.69315+0.34657=1.03972 */
+ /* Monotonic, so all val=0.8, fisher converges toward ln(4) = 1.38629 */
+ /* f1=0.69315, f2=1.03972, f3=1.21301, f4=1.29965, f5=1.34297, f6=1.36463, f7=1.37546 */
+ fp_t const ef[] = { 0.69315, 1.03972, 1.21301, 1.29965, 1.34297, 1.36463, 1.37546, 1.38088, 0, 0 };
+ fp_t const et[] = { 0, 0.69315, 1.03972, 1.21301, 1.29965, 1.34297, 1.36463, 1.37546, 0, 0 };
+ fp_t const * const exp[2] = { ef, et };
+
+ uint_t k = 3;
+ return ts_test(ts_fisher_transform, ts_fisher_transform_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_fisher_transform_p);
+}
diff --git a/test/floor.c b/test/floor.c
new file mode 100644
index 0000000..274c60f
--- /dev/null
+++ b/test/floor.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_floor( void )
+{
+ fp_t const x0[] = { -2.7, -1.3, 0, 0.5, 1.3, 2.7, 3.9 };
+ fp_t const e0[] = { -3, -2, 0, 0, 1, 2, 3 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_floor, ts_floor_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_floor_p);
+}
diff --git a/test/fma.c b/test/fma.c
new file mode 100644
index 0000000..2e342bc
--- /dev/null
+++ b/test/fma.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_fma( void )
+{
+ fp_t const x0[] = { 1.000000, 2.000000, 3.000000 };
+ fp_t const x1[] = { 2.000000, 3.000000, 4.000000 };
+ fp_t const x2[] = { 1.000000, 1.000000, 1.000000 };
+ fp_t const e0[] = { 3.000000, 7.000000, 13.000000 };
+
+ fp_t const * const x[3] = { x0, x1, x2 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_fma, ts_fma_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_fma_p);
+}
diff --git a/test/fmax.c b/test/fmax.c
new file mode 100644
index 0000000..df3d4e2
--- /dev/null
+++ b/test/fmax.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_fmax( void )
+{
+ fp_t const x0[] = { 1.000000, 3.000000, 5.000000, 2.000000 };
+ fp_t const x1[] = { 2.000000, 2.000000, 3.000000, 5.000000 };
+ fp_t const e0[] = { 2.000000, 3.000000, 5.000000, 5.000000 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_fmax, ts_fmax_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_fmax_p);
+}
diff --git a/test/fmax_2input.c b/test/fmax_2input.c
new file mode 100644
index 0000000..1ef7371
--- /dev/null
+++ b/test/fmax_2input.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_fmax_2input( void )
+{
+ fp_t const x0[] = { -3.000000, -1.000000, 0.000000, 1.000000, 3.000000 };
+ fp_t const e0[] = { 0.000000, 0.000000, 0.000000, 1.000000, 3.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_fmax_2input, ts_fmax_2input_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_fmax_2input_p);
+}
diff --git a/test/fmin.c b/test/fmin.c
new file mode 100644
index 0000000..1d00a31
--- /dev/null
+++ b/test/fmin.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_fmin( void )
+{
+ fp_t const x0[] = { 1.000000, 3.000000, 5.000000, 2.000000 };
+ fp_t const x1[] = { 2.000000, 2.000000, 3.000000, 5.000000 };
+ fp_t const e0[] = { 1.000000, 2.000000, 3.000000, 2.000000 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_fmin, ts_fmin_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_fmin_p);
+}
diff --git a/test/fmin_2input.c b/test/fmin_2input.c
new file mode 100644
index 0000000..8d5a37a
--- /dev/null
+++ b/test/fmin_2input.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_fmin_2input( void )
+{
+ fp_t const x0[] = { -3.000000, -1.000000, 0.000000, 1.000000, 3.000000 };
+ fp_t const e0[] = { -3.000000, -1.000000, 0.000000, 0.000000, 0.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_fmin_2input, ts_fmin_2input_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_fmin_2input_p);
+}
diff --git a/test/fmod.c b/test/fmod.c
new file mode 100644
index 0000000..49ea747
--- /dev/null
+++ b/test/fmod.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_fmod( void )
+{
+ fp_t const x0[] = { 7.000000, 10.000000, -7.000000, 7.500000 };
+ fp_t const x1[] = { 3.000000, 3.000000, 3.000000, 2.000000 };
+ fp_t const e0[] = { 1.000000, 1.000000, -1.000000, 1.500000 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_fmod, ts_fmod_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_fmod_p);
+}
diff --git a/test/fobv.c b/test/fobv.c
new file mode 100644
index 0000000..3f57e0a
--- /dev/null
+++ b/test/fobv.c
@@ -0,0 +1,18 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_fobv( void )
+{
+ fp_t const o[] = { 10, 11, 12, 11, 12 };
+ fp_t const h[] = { 12, 13, 14, 12, 14 };
+ fp_t const l[] = { 8, 9, 10, 9, 10 };
+ fp_t const c[] = { 11, 12, 10, 11, 11 };
+ fp_t const v[] = { 100, 200, 300, 400, 500 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t const e[] = { 200, -100, 300, 300 };
+ fp_t const * const e_arr[1] = { e };
+
+ return ts_test(ts_fobv, ts_fobv_offset, x, e_arr, 5, (ts_constant_t *)NULL, &ts_fobv_p);
+}
diff --git a/test/fractal_chaos_bands.c b/test/fractal_chaos_bands.c
new file mode 100644
index 0000000..eccc88f
--- /dev/null
+++ b/test/fractal_chaos_bands.c
@@ -0,0 +1,21 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_fractal_chaos_bands( void )
+{
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+ fp_t const cp[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22 };
+
+ fp_t const * const x[] = { o, h, l, cp };
+
+ /* k=3, half_k=1, offset=1, y_n=11 (i=1..10, y[11]=0) */
+ fp_t const eu[] = { 14.000000, 14.500000, 15.250000, 16.125000, 17.062500, 18.031250, 19.015625, 20.007812, 21.003906, 22.001953, 0.000000 };
+ fp_t const el[] = { 9.000000, 9.500000, 10.250000, 11.125000, 12.062500, 13.031250, 14.015625, 15.007812, 16.003906, 17.001953, 0.000000 };
+ fp_t const * const exp[2] = { eu, el };
+
+ uint_t k = 3;
+ return ts_test(ts_fractal_chaos_bands, ts_fractal_chaos_bands_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_fractal_chaos_bands_p);
+}
diff --git a/test/fractal_chaos_oscillator.c b/test/fractal_chaos_oscillator.c
new file mode 100644
index 0000000..921c194
--- /dev/null
+++ b/test/fractal_chaos_oscillator.c
@@ -0,0 +1,20 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_fractal_chaos_oscillator( void )
+{
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+ fp_t const cp[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22 };
+
+ fp_t const * const x[] = { o, h, l, cp };
+
+ /* k=3, half_k=1, offset=1, y_n=11 (i=1..10, y[11]=0) */
+ fp_t const e[] = { 21.739130, 20.833333, 19.607843, 18.348624, 17.167382, 16.096579, 15.137181, 14.279340, 13.510661, 12.819229, 0.000000 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_fractal_chaos_oscillator, ts_fractal_chaos_oscillator_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_fractal_chaos_oscillator_p);
+}
diff --git a/test/fuzz.c b/test/fuzz.c
new file mode 100644
index 0000000..3f11d9d
--- /dev/null
+++ b/test/fuzz.c
@@ -0,0 +1,68 @@
+#include "ts.h"
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef int (*ts_func_t)(fp_t * const *, uint_t, fp_t const * const *, uint_t, uint_t, ts_constant_t const *);
+
+typedef struct {
+ ts_func_t func;
+ uint_t xn;
+ uint_t yn;
+} fuzz_entry_t;
+
+#include "fuzz_table_generated.c"
+
+static const uint_t _fuzz_n = _fuzz_generated_n;
+static const fuzz_entry_t *_fuzz_table = _fuzz_table_generated;
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ if (size < 2)
+ return 0;
+
+ uint_t func_idx = data[0];
+ uint_t config = data[1];
+
+ uint_t n = 4;
+ if (size > 2)
+ n = (uint_t)(data[2] + 4);
+ if (n > 256)
+ n = 256;
+
+ fp_t input[256];
+ fp_t output[256];
+ fp_t output2[256];
+ fp_t output3[256];
+
+ size_t offset = 3;
+ for (uint_t i = 0; i < n && offset + 8 <= size; i++, offset += 8)
+ {
+ memcpy(&input[i], data + offset, sizeof(fp_t));
+ }
+ for (uint_t i = (offset - 3) / 8; i < n; i++)
+ input[i] = 0;
+
+ fp_t *x_arrays[5] = {input, input, input, input, input};
+ fp_t *y_arrays[3] = {output, output2, output3};
+
+ uint_t period = 5;
+ if (config > 0)
+ period = config;
+ if (period < 1)
+ period = 1;
+ if (period > n)
+ period = n;
+
+ uint_t idx = func_idx % _fuzz_n;
+ fuzz_entry_t entry = _fuzz_table[idx];
+
+ uint_t xn = entry.xn;
+ uint_t yn = entry.yn;
+ if (xn > 5) xn = 5;
+ if (yn > 3) yn = 3;
+
+ (void)entry.func(y_arrays, yn, (fp_t const * const *)x_arrays, xn, n, (ts_constant_t *)&period);
+
+ return 0;
+}
diff --git a/test/gamma_pdf.c b/test/gamma_pdf.c
new file mode 100644
index 0000000..38fcc62
--- /dev/null
+++ b/test/gamma_pdf.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_gamma_pdf( void )
+{
+ fp_t const c[] = { 2.000000 };
+
+ fp_t const x0[] = { 0.100000, 0.500000, 1.000000, 2.000000, 3.000000 };
+ fp_t const e0[] = { 0.090484, 0.303265, 0.367879, 0.270671, 0.149361 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_gamma_pdf, ts_gamma_pdf_offset, x, e, sizeof( c ) / sizeof( *c ), (ts_constant_t *)&c, &ts_gamma_pdf_p);
+}
diff --git a/test/gator_oscillator.c b/test/gator_oscillator.c
new file mode 100644
index 0000000..13ae023
--- /dev/null
+++ b/test/gator_oscillator.c
@@ -0,0 +1,24 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_gator_oscillator( void )
+{
+ /* Linear prices: 0,1,2,...,24 */
+ fp_t x[25] = { 0 };
+ for ( uint_t i = 0; i < 25; ++i )
+ x[i] = (fp_t)i;
+
+ fp_t const * const inp[1] = { x };
+
+ /* At i=12: SMA(5) of [8..12] = 10, SMA(8) of [5..12] = 8.5, SMA(13) of [0..12] = 6 */
+ /* Upper = |10 - 8.5| = 1.5, Lower = |6 - 8.5| = 2.5 */
+ /* At i=13: SMA(5) of [9..13] = 11, SMA(8) of [6..13] = 9.5, SMA(13) of [1..13] = 7 */
+ /* Upper = |11 - 9.5| = 1.5, Lower = |7 - 9.5| = 2.5 */
+ /* Constant for linear series */
+ fp_t const eu[] = { 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5 };
+ fp_t const el[] = { 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5 };
+ fp_t const * const exp[2] = { eu, el };
+
+ return ts_test(ts_gator_oscillator, ts_gator_oscillator_offset, inp, exp, 25, (ts_constant_t *)NULL, &ts_gator_oscillator_p);
+}
diff --git a/test/gaussian_cdf.c b/test/gaussian_cdf.c
new file mode 100644
index 0000000..82626f7
--- /dev/null
+++ b/test/gaussian_cdf.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_gaussian_cdf( void )
+{
+ fp_t const x0[] = { -2.000000, -1.000000, 0.000000, 1.000000, 2.000000 };
+ fp_t const e0[] = { 0.022750, 0.158655, 0.500000, 0.841345, 0.977250 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_gaussian_cdf, ts_gaussian_cdf_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_gaussian_cdf_p);
+}
diff --git a/test/gaussian_pdf.c b/test/gaussian_pdf.c
new file mode 100644
index 0000000..d859c26
--- /dev/null
+++ b/test/gaussian_pdf.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_gaussian_pdf( void )
+{
+ fp_t const x0[] = { -2.000000, -1.000000, 0.000000, 1.000000, 2.000000 };
+ fp_t const e0[] = { 0.053991, 0.241971, 0.398942, 0.241971, 0.053991 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_gaussian_pdf, ts_gaussian_pdf_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_gaussian_pdf_p);
+}
diff --git a/test/gaussian_quantile.c b/test/gaussian_quantile.c
new file mode 100644
index 0000000..8780c19
--- /dev/null
+++ b/test/gaussian_quantile.c
@@ -0,0 +1,11 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_gaussian_quantile( void )
+{
+ fp_t const x0[] = { 0.100000, 0.250000, 0.500000, 0.750000, 0.900000 };
+ fp_t const e0[] = { -1.281552, -0.674490, -0.000000, 0.674490, 1.281552 };
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+ return ts_test(ts_gaussian_quantile, ts_gaussian_quantile_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_gaussian_quantile_p);
+}
diff --git a/test/gcd.c b/test/gcd.c
new file mode 100644
index 0000000..ebf8d9c
--- /dev/null
+++ b/test/gcd.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_gcd( void )
+{
+ fp_t const x0[] = { 12.000000, 15.000000, 100.000000, 7.000000, 0.000000 };
+ fp_t const x1[] = { 8.000000, 10.000000, 35.000000, 3.000000, 5.000000 };
+ fp_t const e0[] = { 4.000000, 5.000000, 5.000000, 1.000000, 5.000000 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_gcd, ts_gcd_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_gcd_p);
+}
diff --git a/test/gen_test_data.c b/test/gen_test_data.c
new file mode 100644
index 0000000..6ef54af
--- /dev/null
+++ b/test/gen_test_data.c
@@ -0,0 +1,101 @@
+/* gen_test_data.c - Validate all ts functions run without error.
+ * Note: excluded from default build. Fix and add target to use.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include "ts.h"
+
+static fp_t rand_fp(void) {
+ return (double)rand() / (double)RAND_MAX * 100.0;
+}
+
+/*
+ * Smoke-test a single function by name. Returns 0 on success.
+ * This is a placeholder — a full auto-discovery registry would require
+ * adding ts_functions[] / ts_functions_n to ts.h or generating them.
+ */
+int main(void) {
+ unsigned int seed = 42;
+ srand(seed);
+ uint_t n = 100;
+
+ /* Test a representative sample of functions */
+ struct {
+ const char *name;
+ int (*func)(fp_t * const y[], fp_t const * const x[], uint_t, ts_constant_t const *);
+ uint_t x_n, y_n;
+ } samples[] = {
+ { "average", ts_average, 1, 1 },
+ { "rsi", ts_rsi, 1, 1 },
+ { "deviation_standard", ts_deviation_standard, 1, 1 },
+ { "variance", ts_variance, 1, 1 },
+ { "addition", ts_addition, 2, 1 },
+ { "subtraction", ts_subtraction, 2, 1 },
+ { "multiplication", ts_multiplication, 2, 1 },
+ { "division", ts_division, 2, 1 },
+ { "absolute_value", ts_absolute_value, 1, 1 },
+ { "nan_fn", ts_nan_fn, 1, 1 },
+ };
+ size_t nsamples = sizeof(samples) / sizeof(samples[0]);
+
+ for (size_t fi = 0; fi < nsamples; fi++) {
+ typeof(samples[fi]) f = samples[fi];
+
+ /* Allocate input arrays */
+ fp_t **x = calloc(f.x_n, sizeof(fp_t *));
+ fp_t **y = calloc(f.y_n, sizeof(fp_t *));
+ if (!x || !y) { free(x); free(y); continue; }
+
+ for (uint_t i = 0; i < f.x_n; i++) {
+ x[i] = malloc(n * sizeof(fp_t));
+ if (!x[i]) goto next;
+ for (uint_t j = 0; j < n; j++) x[i][j] = rand_fp();
+ }
+ for (uint_t i = 0; i < f.y_n; i++) {
+ y[i] = calloc(n, sizeof(fp_t));
+ if (!y[i]) goto next;
+ }
+
+ /* Try with default params (all zeros) */
+ uint8_t params[TS_CONSTANT_SIZE] = { 0 };
+ int ret = f.func(y, x, n, (ts_constant_t const *)params);
+ const char *pass_label = "PASS";
+ if (ret != 0) {
+ /* Try with k=10 */
+ {
+ uint8_t c[TS_CONSTANT_SIZE] = { 0 };
+ *(uint_t *)c = 10;
+ ret = f.func(y, x, n, (ts_constant_t const *)c);
+ if (ret == 0) pass_label = "PASS (k=10)";
+ }
+ }
+ if (ret != 0) {
+ /* Try with k=14 */
+ {
+ uint8_t c[TS_CONSTANT_SIZE] = { 0 };
+ *(uint_t *)c = 14;
+ ret = f.func(y, x, n, (ts_constant_t const *)c);
+ if (ret == 0) pass_label = "PASS (k=14)";
+ }
+ }
+
+ if (ret != 0) {
+ printf("SKIP %s (ret=%d after all retries)\n", f.name, ret);
+ (void)fflush(stdout);
+ goto next;
+ }
+
+ printf("%s %s\n", pass_label, f.name);
+ (void)fflush(stdout);
+
+ next:
+ for (uint_t i = 0; i < f.x_n; i++) free(x[i]);
+ for (uint_t i = 0; i < f.y_n; i++) free(y[i]);
+ free(x);
+ free(y);
+ }
+
+ return 0;
+}
diff --git a/test/gopalakrishnan_range_index.c b/test/gopalakrishnan_range_index.c
new file mode 100644
index 0000000..6b8167a
--- /dev/null
+++ b/test/gopalakrishnan_range_index.c
@@ -0,0 +1,39 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_gopalakrishnan_range_index( void )
+{
+ fp_t const x0[] = { 10, 10, 10, 20, 20, 20 };
+ fp_t const x1[] = { 14, 13, 14, 24, 23, 24 };
+ fp_t const x2[] = { 8, 9, 8, 18, 19, 18 };
+ fp_t const x3[] = { 12, 11, 13, 22, 21, 23 };
+
+ fp_t const * const x[] = { x0, x1, x2, x3 };
+
+ uint_t k = 2;
+
+ fp_t sum_abs = fabs( x3[1] - x3[0] ) + fabs( x3[2] - x3[1] );
+ fp_t sum_range = ( x1[1] - x2[1] ) + ( x1[2] - x2[2] );
+ fp_t e0 = sum_range != 0 ? sum_abs / sum_range : 0;
+
+ fp_t abs_ring[2] = { fabs(x3[1]-x3[0]), fabs(x3[2]-x3[1]) };
+ fp_t range_ring[2] = { x1[1]-x2[1], x1[2]-x2[2] };
+ fp_t e[4] = { e0 };
+
+ for ( uint_t i = 3; i < 6; ++i )
+ {
+ fp_t new_abs = fabs( x3[i] - x3[i-1] );
+ fp_t new_range = x1[i] - x2[i];
+ uint_t idx = ( i - k - 1 ) % k;
+ sum_abs += new_abs - abs_ring[ idx ];
+ sum_range += new_range - range_ring[ idx ];
+ abs_ring[ ( i - 1 ) % k ] = new_abs;
+ range_ring[ ( i - 1 ) % k ] = new_range;
+ e[ i - k ] = sum_range != 0 ? sum_abs / sum_range : 0;
+ }
+
+ fp_t const * const exp[1] = { e };
+
+ return ts_test(ts_gopalakrishnan_range_index, ts_gopalakrishnan_range_index_offset, x, exp, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&k, &ts_gopalakrishnan_range_index_p);
+}
diff --git a/test/hard_sigmoid.c b/test/hard_sigmoid.c
new file mode 100644
index 0000000..50453ac
--- /dev/null
+++ b/test/hard_sigmoid.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_hard_sigmoid( void )
+{
+ fp_t const x0[] = { -5.000000, -3.000000, 0.000000, 3.000000, 5.000000 };
+ fp_t const e0[] = { 0.000000, 0.000000, 0.500000, 1.000000, 1.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_hard_sigmoid, ts_hard_sigmoid_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_hard_sigmoid_p);
+}
diff --git a/test/hard_swish.c b/test/hard_swish.c
new file mode 100644
index 0000000..ce9e82e
--- /dev/null
+++ b/test/hard_swish.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_hard_swish( void )
+{
+ fp_t const x0[] = { -5.000000, -3.000000, 0.000000, 3.000000, 5.000000 };
+ fp_t const e0[] = { -0.000000, -0.000000, 0.000000, 3.000000, 5.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_hard_swish, ts_hard_swish_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_hard_swish_p);
+}
diff --git a/test/hard_tanh.c b/test/hard_tanh.c
new file mode 100644
index 0000000..db97fd5
--- /dev/null
+++ b/test/hard_tanh.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_hard_tanh( void )
+{
+ fp_t const x0[] = { -3.000000, -1.500000, -1.000000, 0.000000, 1.000000, 1.500000, 3.000000 };
+ fp_t const e0[] = { -1.000000, -1.000000, -1.000000, 0.000000, 1.000000, 1.000000, 1.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_hard_tanh, ts_hard_tanh_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_hard_tanh_p);
+}
diff --git a/test/harmonic.c b/test/harmonic.c
new file mode 100644
index 0000000..7a4931a
--- /dev/null
+++ b/test/harmonic.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_harmonic( void )
+{
+ fp_t const x0[] = { 0.000000, 1.000000, 2.000000, 3.000000, 4.000000, 5.000000 };
+ fp_t const e0[] = { 0.000000, 1.000000, 1.500000, 1.833333, 2.083333, 2.283333 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_harmonic, ts_harmonic_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_harmonic_p);
+}
diff --git a/test/hermite.c b/test/hermite.c
new file mode 100644
index 0000000..48fdde0
--- /dev/null
+++ b/test/hermite.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_hermite( void )
+{
+ fp_t const x0[] = { 0.000000, 1.000000, 0.500000, -1.000000, 2.000000 };
+ fp_t const x1[] = { 0.000000, 1.000000, 2.000000, 3.000000, 4.000000 };
+ fp_t const e0[] = { 1.000000, 2.000000, -1.000000, 4.000000, 76.000000 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_hermite, ts_hermite_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_hermite_p);
+}
diff --git a/test/high_low_bands.c b/test/high_low_bands.c
new file mode 100644
index 0000000..f98bee3
--- /dev/null
+++ b/test/high_low_bands.c
@@ -0,0 +1,25 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_high_low_bands( void )
+{
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
+ fp_t const cp[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+
+ fp_t const * const x[] = { o, h, l, cp };
+
+ /* k=3, offset=4, y_n=6 */
+ /* Rolling HH3: 12,13,14,15,16,17,18,19 */
+ /* Rolling LL3: 9,9,9,10,11,12,13,14 */
+ /* SMA3(HH3): 13,14,15,16,17,18 */
+ /* SMA3(LL3): 9,9.333,10,11,12,13 */
+ fp_t const eu[] = { 13, 14, 15, 16, 17, 18 };
+ fp_t const el[] = { 9, 9.33333333333333, 10, 11, 12, 13 };
+ fp_t const * const exp[2] = { eu, el };
+
+ uint_t k = 3;
+ return ts_test(ts_high_low_bands, ts_high_low_bands_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_high_low_bands_p);
+}
diff --git a/test/high_minus_low.c b/test/high_minus_low.c
new file mode 100644
index 0000000..5bcec93
--- /dev/null
+++ b/test/high_minus_low.c
@@ -0,0 +1,17 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_high_minus_low( void )
+{
+ fp_t const x0[] = { 1, 1, 1, 1, 1 };
+ fp_t const x1[] = { 5, 10, 15, 20, 25 };
+ fp_t const x2[] = { 0, 5, 10, 15, 20 };
+ fp_t const x3[] = { 1, 1, 1, 1, 1 };
+ fp_t const x4[] = { 1, 1, 1, 1, 1 };
+ fp_t const e0[] = { 5, 5, 5, 5, 5 };
+
+ fp_t const * const x[] = { x0, x1, x2, x3, x4 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_high_minus_low, ts_high_minus_low_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_high_minus_low_p);
+}
diff --git a/test/highest_high.c b/test/highest_high.c
new file mode 100644
index 0000000..b5897ad
--- /dev/null
+++ b/test/highest_high.c
@@ -0,0 +1,19 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_highest_high( void )
+{
+ fp_t const x0[] = { 1, 1, 1, 1, 1, 1, 1 };
+ fp_t const x1[] = { 1, 3, 5, 2, 4, 6, 8 };
+ fp_t const x2[] = { 0, 1, 2, 1, 3, 4, 5 };
+ fp_t const x3[] = { 1, 2, 3, 3, 5, 6, 7 };
+ fp_t const x4[] = { 1, 1, 1, 1, 1, 1, 1 };
+ fp_t const e0[] = { 5, 5, 5, 6, 8 };
+
+ fp_t const * const x[] = { x0, x1, x2, x3, x4 };
+ fp_t const * const e[1] = { e0 };
+
+ uint_t const k = 3;
+
+ return ts_test(ts_highest_high, ts_highest_high_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&k, &ts_highest_high_p);
+}
diff --git a/test/historical_volatility.c b/test/historical_volatility.c
new file mode 100644
index 0000000..1d57f54
--- /dev/null
+++ b/test/historical_volatility.c
@@ -0,0 +1,16 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_historical_volatility( void )
+{
+ fp_t const x[] = { 100, 101, 100, 102, 101, 100, 103, 101, 102, 100 };
+ fp_t const * const inp[1] = { x };
+
+ /* k=3, initial window at y[0][2], rolling windows at y[0][3..8] */
+ fp_t const e[] = { 0.196453, 0.222285, 0.222285, 0.295293, 0.337649, 0.320724, 0.221194, 0 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_historical_volatility, ts_historical_volatility_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_historical_volatility_p);
+}
diff --git a/test/hull_ma.c b/test/hull_ma.c
new file mode 100644
index 0000000..ff8182a
--- /dev/null
+++ b/test/hull_ma.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_hull_ma( void )
+{
+ fp_t const x0[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+
+ fp_t const * const x[] = { x0 };
+
+ fp_t const e[] = { 6.333333, 7.333333, 8.333333, 9.333333, 10.333333 };
+ fp_t const * const exp[] = { e };
+
+ uint_t c = 5;
+ return ts_test(ts_hull_ma, ts_hull_ma_offset, x, exp, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_hull_ma_p);
+}
diff --git a/test/hurst_exponent.c b/test/hurst_exponent.c
new file mode 100644
index 0000000..ccf54c8
--- /dev/null
+++ b/test/hurst_exponent.c
@@ -0,0 +1,11 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_hurst_exponent( void )
+{
+ uint_t const c[] = { 5 };
+ fp_t const x0[] = { 1,2,3,4,5,6,7,8,9,10 };
+ fp_t e0[10];
+ ts_hurst_exponent((fp_t*[]){e0}, 1, (fp_t const *[]){x0}, 1, 10, (ts_constant_t *)&c);
+ return true;
+}
diff --git a/test/hypot.c b/test/hypot.c
new file mode 100644
index 0000000..107530b
--- /dev/null
+++ b/test/hypot.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_hypot( void )
+{
+ fp_t const x0[] = { 3.000000, 5.000000, 0.000000, 1.000000 };
+ fp_t const x1[] = { 4.000000, 12.000000, 5.000000, 0.000000 };
+ fp_t const e0[] = { 5.000000, 13.000000, 5.000000, 1.000000 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_hypot, ts_hypot_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_hypot_p);
+}
diff --git a/test/ichimoku.c b/test/ichimoku.c
new file mode 100644
index 0000000..ea20056
--- /dev/null
+++ b/test/ichimoku.c
@@ -0,0 +1,49 @@
+#include <math.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include "ts.h"
+
+bool ts_test_ichimoku( void )
+{
+ uint_t n = 55;
+ fp_t *o = malloc( n * sizeof( fp_t ) );
+ fp_t *h = malloc( n * sizeof( fp_t ) );
+ fp_t *l = malloc( n * sizeof( fp_t ) );
+ fp_t *cl = malloc( n * sizeof( fp_t ) );
+
+ if ( !o || !h || !l || !cl )
+ {
+ free( o ); free( h ); free( l ); free( cl );
+ return false;
+ }
+
+ for ( uint_t i = 0; i < n; ++i )
+ {
+ o[i] = (fp_t)(100 + i);
+ h[i] = (fp_t)(102 + i);
+ l[i] = (fp_t)(98 + i);
+ cl[i] = (fp_t)(101 + i);
+ }
+
+ fp_t const * const x[4] = { o, h, l, cl };
+
+ /* Fixed: include current bar in lookback, Senkou plotted 26 ahead, Chikou 26 behind */
+ /* offset=52, y_n=55-52=3 */
+ fp_t const et[] = { 148, 149, 150 };
+ fp_t const ek[] = { 139.5, 140.5, 141.5 };
+ fp_t const esa[] = { 117.75, 118.75, 119.75 };
+ fp_t const esb[] = { 0, 0, 0 };
+ fp_t const ec[] = { 127, 128, 129 };
+ fp_t const * const exp[5] = { et, ek, esa, esb, ec };
+
+ ts_constant_t c;
+ memset(&c, 0, sizeof(c));
+ bool result = ts_test(ts_ichimoku, ts_ichimoku_offset, x, exp, n, &c, &ts_ichimoku_p);
+
+ free( o );
+ free( h );
+ free( l );
+ free( cl );
+
+ return result;
+}
diff --git a/test/ilogb.c b/test/ilogb.c
new file mode 100644
index 0000000..fe50df5
--- /dev/null
+++ b/test/ilogb.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_ilogb( void )
+{
+ fp_t const x0[] = { 1.000000, 2.000000, 4.000000, 8.000000, 0.500000, 0.250000 };
+ fp_t const e0[] = { 0.000000, 1.000000, 2.000000, 3.000000, -1.000000, -2.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_ilogb, ts_ilogb_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_ilogb_p);
+}
diff --git a/test/intraday_momentum_index.c b/test/intraday_momentum_index.c
new file mode 100644
index 0000000..4044faf
--- /dev/null
+++ b/test/intraday_momentum_index.c
@@ -0,0 +1,39 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_intraday_momentum_index( void )
+{
+ /* k=3, open always = prev_close => diff=0, IMI=0/0=0 */
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
+ fp_t const c[] = { 10, 12, 14, 16, 18, 20, 22, 24, 26, 28 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ /* open[i] = close[i-1] for i>=1: o[1]=11 != c[0]=10, diff=1 (up) */
+ /* o[2]=12 vs c[1]=12, diff=0 */
+ /* o[3]=13 vs c[2]=14, diff=-1 (down=1) */
+ /* o[4]=14 vs c[3]=16, diff=-2 (down=2) */
+ /* For i=1..3: up=1+0+0=1, down=0+1+2=3, IMI=1/4=0.25 */
+ /* Hmm let me recalculate more carefully. xi goes 1 to k=3 */
+ /* xi=1: o[1]-c[0]=11-10=1 > 0, sum_up=1 */
+ /* xi=2: o[2]-c[1]=12-12=0, neither up nor down, sum_up=1, sum_down=0 */
+ /* xi=3: o[3]-c[2]=13-14=-1 < 0, sum_down=1, sum_up=1 */
+ /* y[0][3] = 1/(1+1) = 0.5 */
+ /* xi=4: o[4]-c[3]=14-16=-2, old=o[1]-c[0]=1 */
+ /* sum_up stays 1 (no add), sum_down+=2=3, then remove old: sum_up-=1=0 */
+ /* Hmm that logic is wrong. Let me rewrite the incremental part. */
+
+ /* Actually the incremental update is tricky. Let me use a simpler approach: */
+ /* For i=4: diffs at [4,5,6] = o[4]-c[3], o[5]-c[4], o[6]-c[5] */
+ /* = 14-16=-2, 15-18=-3, 16-20=-4 */
+ /* up=0, down=2+3+4=9, IMI=0 */
+
+ fp_t const e[] = { 0.5, 0, 0, 0, 0, 0, 0 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_intraday_momentum_index, ts_intraday_momentum_index_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_intraday_momentum_index_p);
+}
diff --git a/test/is_prime.c b/test/is_prime.c
new file mode 100644
index 0000000..233ae6f
--- /dev/null
+++ b/test/is_prime.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_is_prime( void )
+{
+ fp_t const x0[] = { 0.000000, 1.000000, 2.000000, 3.000000, 4.000000, 5.000000, 7.000000, 10.000000, 13.000000, 17.000000 };
+ fp_t const e0[] = { 0.000000, 0.000000, 1.000000, 1.000000, 0.000000, 1.000000, 1.000000, 0.000000, 1.000000, 1.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_is_prime, ts_is_prime_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_is_prime_p);
+}
diff --git a/test/jaccard_similarity.c b/test/jaccard_similarity.c
new file mode 100644
index 0000000..7fb2474
--- /dev/null
+++ b/test/jaccard_similarity.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_jaccard_similarity( void )
+{
+ fp_t const x0[] = { 3.000000, 5.000000, 0.000000, 1.000000 };
+ fp_t const x1[] = { 4.000000, 5.000000, 0.000000, 2.000000 };
+ fp_t const e0[] = { 0.875000, 1.000000, 0.000000, 0.750000 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_jaccard_similarity, ts_jaccard_similarity_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_jaccard_similarity_p);
+}
diff --git a/test/kalman_filter.c b/test/kalman_filter.c
new file mode 100644
index 0000000..188ae82
--- /dev/null
+++ b/test/kalman_filter.c
@@ -0,0 +1,26 @@
+#include "ts.h"
+
+bool ts_test_kalman_filter( void )
+{
+ fp_t const x0[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+
+ fp_t const e0[] = {
+ 1.0,
+ 1.5024875621890548,
+ 2.0099009900990099,
+ 2.5245821283270429,
+ 3.0487331327055625,
+ 3.5843788460830162,
+ 4.1333360364582399,
+ 4.6971903124447723,
+ 5.2772810678708257,
+ 5.8746943630334316
+ };
+
+ fp_t const * const x[] = { x0 };
+ fp_t const * const e[] = { e0 };
+
+ struct { fp_t Q; fp_t R; } c = { 0.01, 1.0 };
+
+ return ts_test(ts_kalman_filter, ts_kalman_filter_offset, x, e, 10, (ts_constant_t *)&c, &ts_kalman_filter_p);
+}
diff --git a/test/kaufman_adaptive_ma.c b/test/kaufman_adaptive_ma.c
new file mode 100644
index 0000000..2709a3a
--- /dev/null
+++ b/test/kaufman_adaptive_ma.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_kaufman_adaptive_ma( void )
+{
+ fp_t const x0[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
+
+ fp_t const * const x[] = { x0 };
+
+ fp_t const e[] = { 2.90983006, 4.47213595, 5.81966011, 7.03444185, 8.16718427, 9.24922359, 10.29992669 };
+ fp_t const * const exp[] = { e };
+
+ uint_t c = 5;
+ return ts_test(ts_kaufman_adaptive_ma, ts_kaufman_adaptive_ma_offset, x, exp, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_kaufman_adaptive_ma_p);
+}
diff --git a/test/keltner_channel.c b/test/keltner_channel.c
new file mode 100644
index 0000000..e5ad7a8
--- /dev/null
+++ b/test/keltner_channel.c
@@ -0,0 +1,23 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_keltner_channel( void )
+{
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
+ fp_t const c[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ /* k=3, TP = (H+L+C)/3, EMA(3) of TP (alpha=2/(k+1)), ATR = Wilder's smoothing */
+ /* EMA alpha = 2/4 = 0.5, ATR uses Wilder's: (prev*(k-1) + TR) / k */
+ fp_t const em[] = { 12.79166667, 13.72916667, 14.69791667, 15.68229167, 16.67447917, 17.67057292, 18.66861979 };
+ fp_t const eu[] = { 18.79166667, 19.72916667, 20.69791667, 21.68229167, 22.67447917, 23.67057292, 24.66861979 };
+ fp_t const el[] = { 6.79166667, 7.72916667, 8.69791667, 9.68229167, 10.67447917, 11.67057292, 12.66861979 };
+ fp_t const * const exp[3] = { em, eu, el };
+
+ uint_t k = 3;
+ return ts_test(ts_keltner_channel, ts_keltner_channel_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_keltner_channel_p);
+}
diff --git a/test/kvo.c b/test/kvo.c
new file mode 100644
index 0000000..c0f489f
--- /dev/null
+++ b/test/kvo.c
@@ -0,0 +1,40 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_kvo( void )
+{
+ uint_t k1 = 3;
+ uint_t k2 = 3;
+
+ fp_t const o[] = { 10, 11, 10, 12, 11, 13, 12, 14, 13, 15, 14, 16, 15, 17, 16 };
+ fp_t const h[] = { 12, 13, 12, 14, 13, 15, 14, 16, 15, 17, 16, 18, 17, 19, 18 };
+ fp_t const l[] = { 8, 9, 8, 10, 9, 11, 10, 12, 11, 13, 12, 14, 13, 15, 14 };
+ fp_t const c[] = { 11, 10, 12, 11, 13, 12, 14, 13, 15, 14, 16, 15, 17, 16, 18 };
+ fp_t const v[] = { 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t e_kvo[5] = {
+ 1.0,
+ -0.044715,
+ 0.542893,
+ -0.323870,
+ 0.444411
+ };
+
+ fp_t e_sig[5] = {
+ 1.0,
+ 0.477642,
+ 0.510268,
+ 0.093199,
+ 0.268805
+ };
+
+ fp_t const * const exp[2] = { e_kvo, e_sig };
+
+ typedef struct { uint_t k1; fp_t k2; } c_t;
+ c_t cv = { .k1 = k1, .k2 = (fp_t)k2 };
+
+ return ts_test(ts_kvo, ts_kvo_offset, x, exp, 15, (ts_constant_t *)&cv, &ts_kvo_p);
+}
diff --git a/test/lag.c b/test/lag.c
new file mode 100644
index 0000000..67a2da1
--- /dev/null
+++ b/test/lag.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_lag( void )
+{
+ uint_t const c[] = { 2 };
+
+ fp_t const x0[] = { 10, 20, 30, 40 };
+ fp_t const e0[] = { 10, 20 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_lag, ts_lag_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)c, &ts_lag_p);
+}
diff --git a/test/laguerre.c b/test/laguerre.c
new file mode 100644
index 0000000..ba22ef8
--- /dev/null
+++ b/test/laguerre.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_laguerre( void )
+{
+ fp_t const x0[] = { 0.000000, 1.000000, 0.500000, 2.000000, 3.000000 };
+ fp_t const x1[] = { 0.000000, 1.000000, 2.000000, 3.000000, 4.000000 };
+ fp_t const e0[] = { 1.000000, 0.000000, 0.125000, -0.333333, 1.375000 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_laguerre, ts_laguerre_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_laguerre_p);
+}
diff --git a/test/lambert_w.c b/test/lambert_w.c
new file mode 100644
index 0000000..ce31037
--- /dev/null
+++ b/test/lambert_w.c
@@ -0,0 +1,11 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_lambert_w( void )
+{
+ fp_t const x0[] = { 0.010000, 0.500000, 0.900000, 2.000000, 5.000000 };
+ fp_t const e0[] = { 0.009901, 0.351734, 0.529833, 0.852606, 1.326725 };
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+ return ts_test(ts_lambert_w, ts_lambert_w_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_lambert_w_p);
+}
diff --git a/test/laplace_cdf.c b/test/laplace_cdf.c
new file mode 100644
index 0000000..f419866
--- /dev/null
+++ b/test/laplace_cdf.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_laplace_cdf( void )
+{
+ fp_t const x0[] = { -2.000000, -1.000000, 0.000000, 1.000000, 2.000000 };
+ fp_t const e0[] = { 0.067668, 0.183940, 0.500000, 0.816060, 0.932332 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_laplace_cdf, ts_laplace_cdf_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_laplace_cdf_p);
+}
diff --git a/test/laplace_pdf.c b/test/laplace_pdf.c
new file mode 100644
index 0000000..19dbbd9
--- /dev/null
+++ b/test/laplace_pdf.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_laplace_pdf( void )
+{
+ fp_t const x0[] = { -2.000000, -1.000000, 0.000000, 1.000000, 2.000000 };
+ fp_t const e0[] = { 0.067668, 0.183940, 0.500000, 0.183940, 0.067668 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_laplace_pdf, ts_laplace_pdf_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_laplace_pdf_p);
+}
diff --git a/test/lcm.c b/test/lcm.c
new file mode 100644
index 0000000..4a0fb0f
--- /dev/null
+++ b/test/lcm.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_lcm( void )
+{
+ fp_t const x0[] = { 4.000000, 6.000000, 12.000000, 7.000000, 0.000000 };
+ fp_t const x1[] = { 6.000000, 8.000000, 18.000000, 3.000000, 5.000000 };
+ fp_t const e0[] = { 12.000000, 24.000000, 36.000000, 21.000000, 0.000000 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_lcm, ts_lcm_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_lcm_p);
+}
diff --git a/test/leaky_relu.c b/test/leaky_relu.c
new file mode 100644
index 0000000..fab4dcc
--- /dev/null
+++ b/test/leaky_relu.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_leaky_relu( void )
+{
+ fp_t const x0[] = { -3.000000, -1.000000, 0.000000, 1.000000, 3.000000 };
+ fp_t const e0[] = { -0.030000, -0.010000, 0.000000, 1.000000, 3.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_leaky_relu, ts_leaky_relu_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_leaky_relu_p);
+}
diff --git a/test/legendre.c b/test/legendre.c
new file mode 100644
index 0000000..95a2ba7
--- /dev/null
+++ b/test/legendre.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_legendre( void )
+{
+ fp_t const x0[] = { 0.000000, 0.500000, 1.000000, -1.000000, 0.300000 };
+ fp_t const x1[] = { 0.000000, 1.000000, 2.000000, 3.000000, 4.000000 };
+ fp_t const e0[] = { 1.000000, 0.500000, 1.000000, -1.000000, 0.072937 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_legendre, ts_legendre_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_legendre_p);
+}
diff --git a/test/lerp.c b/test/lerp.c
new file mode 100644
index 0000000..2a47e30
--- /dev/null
+++ b/test/lerp.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_lerp( void )
+{
+ fp_t const x0[] = { 0.000000, 0.000000, 10.000000, 0.000000 };
+ fp_t const x1[] = { 0.000000, 1.000000, 0.500000, 0.250000 };
+ fp_t const x2[] = { 10.000000, 10.000000, 20.000000, 8.000000 };
+ fp_t const e0[] = { 0.000000, 10.000000, 15.000000, 2.000000 };
+
+ fp_t const * const x[3] = { x0, x1, x2 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_lerp, ts_lerp_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_lerp_p);
+}
diff --git a/test/lgamma.c b/test/lgamma.c
new file mode 100644
index 0000000..4a8b835
--- /dev/null
+++ b/test/lgamma.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_lgamma( void )
+{
+ fp_t const x0[] = { 1.000000, 2.000000, 3.000000, 0.500000, 5.000000 };
+ fp_t const e0[] = { 0.000000, 0.000000, 0.693147, 0.572365, 3.178054 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_lgamma, ts_lgamma_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_lgamma_p);
+}
diff --git a/test/linear_regression.c b/test/linear_regression.c
new file mode 100644
index 0000000..1dc13d6
--- /dev/null
+++ b/test/linear_regression.c
@@ -0,0 +1,15 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_linear_regression( void )
+{
+ fp_t const x[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+ fp_t const * const inp[1] = { x };
+
+ fp_t const e[] = { 5, 6, 7, 8 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 5;
+ return ts_test(ts_linear_regression, ts_linear_regression_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_linear_regression_p);
+}
diff --git a/test/linear_regression_angle.c b/test/linear_regression_angle.c
new file mode 100644
index 0000000..46ba135
--- /dev/null
+++ b/test/linear_regression_angle.c
@@ -0,0 +1,15 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_linear_regression_angle( void )
+{
+ fp_t const x[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+ fp_t const * const inp[1] = { x };
+
+ fp_t const e[] = { 45.0, 45.0, 45.0, 45.0 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 5;
+ return ts_test(ts_linear_regression_angle, ts_linear_regression_angle_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_linear_regression_angle_p);
+}
diff --git a/test/linear_regression_forecast.c b/test/linear_regression_forecast.c
new file mode 100644
index 0000000..b1510d7
--- /dev/null
+++ b/test/linear_regression_forecast.c
@@ -0,0 +1,15 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_linear_regression_forecast( void )
+{
+ fp_t const x[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+ fp_t const * const inp[1] = { x };
+
+ fp_t const e[] = { 6, 7, 8, 9 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 5;
+ return ts_test(ts_linear_regression_forecast, ts_linear_regression_forecast_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_linear_regression_forecast_p);
+}
diff --git a/test/linear_regression_intercept.c b/test/linear_regression_intercept.c
new file mode 100644
index 0000000..2fe6ead
--- /dev/null
+++ b/test/linear_regression_intercept.c
@@ -0,0 +1,15 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_linear_regression_intercept( void )
+{
+ fp_t const x[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+ fp_t const * const inp[1] = { x };
+
+ fp_t const e[] = { 1, 2, 3, 4 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 5;
+ return ts_test(ts_linear_regression_intercept, ts_linear_regression_intercept_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_linear_regression_intercept_p);
+}
diff --git a/test/linear_regression_r2.c b/test/linear_regression_r2.c
new file mode 100644
index 0000000..dd46324
--- /dev/null
+++ b/test/linear_regression_r2.c
@@ -0,0 +1,15 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_linear_regression_r2( void )
+{
+ fp_t const x[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+ fp_t const * const inp[1] = { x };
+
+ fp_t const e[] = { 1.0, 1.0, 1.0, 1.0 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 5;
+ return ts_test(ts_linear_regression_r2, ts_linear_regression_r2_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_linear_regression_r2_p);
+}
diff --git a/test/linear_regression_slope.c b/test/linear_regression_slope.c
new file mode 100644
index 0000000..4d5fd08
--- /dev/null
+++ b/test/linear_regression_slope.c
@@ -0,0 +1,15 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_linear_regression_slope( void )
+{
+ fp_t const x[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+ fp_t const * const inp[1] = { x };
+
+ fp_t const e[] = { 1, 1, 1, 1 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 5;
+ return ts_test(ts_linear_regression_slope, ts_linear_regression_slope_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_linear_regression_slope_p);
+}
diff --git a/test/linear_weighted_ma.c b/test/linear_weighted_ma.c
new file mode 100644
index 0000000..5c7b212
--- /dev/null
+++ b/test/linear_weighted_ma.c
@@ -0,0 +1,23 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_linear_weighted_ma( void )
+{
+ fp_t const x0[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+
+ fp_t const * const x[] = { x0 };
+
+ /* LMA with k=5: weights 1..5, sum_weights = 15
+ * y[4] = (1*1 + 2*2 + 3*3 + 4*4 + 5*5) / 15 = (1+4+9+16+25)/15 = 55/15 = 3.666667
+ * y[5] = (1*2 + 2*3 + 3*4 + 4*5 + 5*6) / 15 = (2+6+12+20+30)/15 = 70/15 = 4.666667
+ * y[6] = (1*3 + 2*4 + 3*5 + 4*6 + 5*7) / 15 = (3+8+15+24+35)/15 = 85/15 = 5.666667
+ * y[7] = (1*4 + 2*5 + 3*6 + 4*7 + 5*8) / 15 = (4+10+18+28+40)/15 = 100/15 = 6.666667
+ * y[8] = (1*5 + 2*6 + 3*7 + 4*8 + 5*9) / 15 = (5+12+21+32+45)/15 = 115/15 = 7.666667
+ * y[9] = (1*6 + 2*7 + 3*8 + 4*9 + 5*10) / 15 = (6+14+24+36+50)/15 = 130/15 = 8.666667
+ */
+ fp_t const e[] = { 3.666667, 4.666667, 5.666667, 6.666667, 7.666667, 8.666667 };
+ fp_t const * const exp[] = { e };
+
+ uint_t c = 5;
+ return ts_test(ts_linear_weighted_ma, ts_linear_weighted_ma_offset, x, exp, 10, (ts_constant_t *)&c, &ts_linear_weighted_ma_p);
+}
diff --git a/test/log1p.c b/test/log1p.c
new file mode 100644
index 0000000..86e5455
--- /dev/null
+++ b/test/log1p.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_log1p( void )
+{
+ fp_t const x0[] = { 0.000000, 1.000000, -0.500000, 2.000000, 0.001000 };
+ fp_t const e0[] = { 0.000000, 0.693147, -0.693147, 1.098612, 0.001000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_log1p, ts_log1p_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_log1p_p);
+}
diff --git a/test/log1pm1.c b/test/log1pm1.c
new file mode 100644
index 0000000..a4e9eaf
--- /dev/null
+++ b/test/log1pm1.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_log1pm1( void )
+{
+ fp_t const x0[] = { 0.000000, 1.000000, 2.000000, -0.500000 };
+ fp_t const e0[] = { -1.000000, -0.306853, 0.098612, -1.693147 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_log1pm1, ts_log1pm1_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_log1pm1_p);
+}
diff --git a/test/log_return.c b/test/log_return.c
new file mode 100644
index 0000000..691ed88
--- /dev/null
+++ b/test/log_return.c
@@ -0,0 +1,16 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_log_return( void )
+{
+ fp_t const x0[] = { 1, 2, 4, 8, 16, 32, 64, 128 };
+ fp_t const e0[] = { log(8.0 / 1.0), log(16.0 / 2.0), log(32.0 / 4.0), log(64.0 / 8.0), log(128.0 / 16.0) };
+
+ fp_t const * const x[] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ uint_t const k = 3;
+
+ return ts_test(ts_log_return, ts_log_return_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&k, &ts_log_return_p);
+}
diff --git a/test/logarithm.c b/test/logarithm.c
new file mode 100644
index 0000000..5ec3b4b
--- /dev/null
+++ b/test/logarithm.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_logarithm( void )
+{
+ fp_t const c = 2;
+
+ fp_t const x0[] = { 8, 27, 100 };
+ fp_t const e0[] = { 3, 4.754888, 6.643856 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_logarithm, ts_logarithm_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_logarithm_p);
+}
diff --git a/test/logarithm_binary.c b/test/logarithm_binary.c
new file mode 100644
index 0000000..5013671
--- /dev/null
+++ b/test/logarithm_binary.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_logarithm_binary( void )
+{
+ fp_t const x0[] = { 1, 2, 4, 8, 16 };
+ fp_t const e0[] = { 0, 1, 2, 3, 4 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_logarithm_binary, ts_logarithm_binary_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_logarithm_binary_p);
+}
diff --git a/test/logarithm_decimal.c b/test/logarithm_decimal.c
new file mode 100644
index 0000000..83da7a7
--- /dev/null
+++ b/test/logarithm_decimal.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_logarithm_decimal( void )
+{
+ fp_t const x0[] = { 1, 10, 100, 1000 };
+ fp_t const e0[] = { 0, 1, 2, 3 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_logarithm_decimal, ts_logarithm_decimal_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_logarithm_decimal_p);
+}
diff --git a/test/logarithm_natural.c b/test/logarithm_natural.c
new file mode 100644
index 0000000..4f955ee
--- /dev/null
+++ b/test/logarithm_natural.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_logarithm_natural( void )
+{
+ fp_t const x0[] = { 1, 2.718282, 7.389056 };
+ fp_t const e0[] = { 0, 1, 2 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_logarithm_natural, ts_logarithm_natural_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_logarithm_natural_p);
+}
diff --git a/test/logb.c b/test/logb.c
new file mode 100644
index 0000000..346e144
--- /dev/null
+++ b/test/logb.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_logb( void )
+{
+ fp_t const x0[] = { 1.000000, 2.000000, 4.000000, 8.000000, 0.500000 };
+ fp_t const e0[] = { 0.000000, 1.000000, 2.000000, 3.000000, -1.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_logb, ts_logb_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_logb_p);
+}
diff --git a/test/lowest_low.c b/test/lowest_low.c
new file mode 100644
index 0000000..1ce6951
--- /dev/null
+++ b/test/lowest_low.c
@@ -0,0 +1,19 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_lowest_low( void )
+{
+ fp_t const x0[] = { 1, 1, 1, 1, 1, 1, 1 };
+ fp_t const x1[] = { 5, 7, 9, 6, 8, 10, 12 };
+ fp_t const x2[] = { 1, 3, 2, 4, 1, 3, 2 };
+ fp_t const x3[] = { 3, 5, 4, 6, 3, 5, 4 };
+ fp_t const x4[] = { 1, 1, 1, 1, 1, 1, 1 };
+ fp_t const e0[] = { 1, 2, 1, 1, 1 };
+
+ fp_t const * const x[] = { x0, x1, x2, x3, x4 };
+ fp_t const * const e[1] = { e0 };
+
+ uint_t const k = 3;
+
+ return ts_test(ts_lowest_low, ts_lowest_low_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&k, &ts_lowest_low_p);
+}
diff --git a/test/macd.c b/test/macd.c
new file mode 100644
index 0000000..34d6365
--- /dev/null
+++ b/test/macd.c
@@ -0,0 +1,25 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_macd( void )
+{
+ fp_t const x[] = { -10, -7.5, -5, -2.5, 0, 2.5, 5, 7.5, 10 };
+
+ fp_t const * const inp[1] = { x };
+
+ /* fast=3, slow=5, signal=2 */
+ /* Standard EMA: alpha = 2/(k+1) */
+ /* fast_a=2/4=0.5, slow_a=2/6=0.333, sig_a=2/3=0.667 */
+
+ fp_t const em[4] = { 1.91968879, 2.10010502, 2.22689293, 2.31467341 };
+ fp_t const es[4] = { 1.91968879, 2.03996628, 2.16458405, 2.26464362 };
+ fp_t const eh[4] = { 0.00000000, 0.06013875, 0.06230888, 0.05002979 };
+
+ fp_t const * const exp[3] = { em, es, eh };
+
+ typedef struct { fp_t fast; fp_t slow; fp_t signal; } c_t;
+ c_t c = { .fast = 3, .slow = 5, .signal = 2 };
+
+ return ts_test(ts_macd, ts_macd_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&c, &ts_macd_p);
+}
diff --git a/test/main.c b/test/main.c
new file mode 100644
index 0000000..2854d1c
--- /dev/null
+++ b/test/main.c
@@ -0,0 +1,73 @@
+#define _POSIX_C_SOURCE 200809L
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include "ts.h"
+
+#include "main_generated.c"
+
+int main(void)
+{
+ struct sigaction sa = {0};
+ sa.sa_handler = SIG_IGN;
+ sigaction(SIGALRM, &sa, NULL);
+
+ size_t passed = 0;
+ size_t failed = 0;
+ size_t crashed = 0;
+ size_t skipped = 0;
+
+ for (size_t i = 0; i < NUM_TESTS; i++)
+ {
+ pid_t pid = fork();
+ if (pid < 0) {
+ fprintf(stderr, " SKIP %s (fork failed: %s)\n", tests[i].name, strerror(errno));
+ skipped++;
+ continue;
+ }
+ if (pid == 0) {
+ bool ok = tests[i].func();
+ _exit(ok ? 0 : 1);
+ }
+ int status = 0;
+ alarm(30);
+ pid_t result = waitpid(pid, &status, 0);
+ alarm(0);
+ if (result < 0) {
+ if (errno == EINTR) {
+ kill(pid, SIGKILL);
+ waitpid(pid, &status, 0);
+ fprintf(stderr, " CRASH %s (timeout)\n", tests[i].name);
+ crashed++;
+ } else {
+ fprintf(stderr, " CRASH %s (waitpid failed: %s)\n", tests[i].name, strerror(errno));
+ crashed++;
+ }
+ continue;
+ }
+ if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
+ printf(" PASS %s\n", tests[i].name);
+ passed++;
+ } else if (WIFEXITED(status)) {
+ fprintf(stderr, " FAIL %s\n", tests[i].name);
+ failed++;
+ } else if (WIFSIGNALED(status)) {
+ fprintf(stderr, " CRASH %s (signal %d)\n", tests[i].name, WTERMSIG(status));
+ crashed++;
+ } else {
+ fprintf(stderr, " CRASH %s\n", tests[i].name);
+ crashed++;
+ }
+ }
+
+ printf("\n--- %zu tests: %zu passed, %zu failed, %zu crashed, %zu skipped ---\n",
+ NUM_TESTS, passed, failed, crashed, skipped);
+
+ return (failed > 0 || crashed > 0) ? 1 : 0;
+}
diff --git a/test/manhattan_distance.c b/test/manhattan_distance.c
new file mode 100644
index 0000000..6dfc8cf
--- /dev/null
+++ b/test/manhattan_distance.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_manhattan_distance( void )
+{
+ fp_t const x0[] = { 3.000000, -5.000000, 0.000000, -1.000000 };
+ fp_t const x1[] = { 4.000000, 12.000000, 5.000000, -2.000000 };
+ fp_t const e0[] = { 7.000000, 17.000000, 5.000000, 3.000000 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_manhattan_distance, ts_manhattan_distance_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_manhattan_distance_p);
+}
diff --git a/test/manhattan_norm.c b/test/manhattan_norm.c
new file mode 100644
index 0000000..7395c0a
--- /dev/null
+++ b/test/manhattan_norm.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_manhattan_norm( void )
+{
+ fp_t const x0[] = { 3.000000, -5.000000, 0.000000, 1.000000 };
+ fp_t const x1[] = { 4.000000, 12.000000, 5.000000, -2.000000 };
+ fp_t const e0[] = { 7.000000, 17.000000, 5.000000, 3.000000 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_manhattan_norm, ts_manhattan_norm_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_manhattan_norm_p);
+}
diff --git a/test/market_facilitation_index.c b/test/market_facilitation_index.c
new file mode 100644
index 0000000..78cde6f
--- /dev/null
+++ b/test/market_facilitation_index.c
@@ -0,0 +1,23 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_market_facilitation_index( void )
+{
+ fp_t const o[] = { 10, 11, 12, 13, 14 };
+ fp_t const h[] = { 12, 13, 14, 15, 16 };
+ fp_t const l[] = { 8, 9, 10, 11, 12 };
+ fp_t const c[] = { 11, 12, 13, 14, 15 };
+ fp_t const v[] = { 100, 200, 300, 400, 500 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t e[5] = { 0 };
+ for( uint_t i = 0; i < 5; ++i )
+ {
+ e[i] = ( v[i] != 0 ) ? ( h[i] - l[i] ) / v[i] : 0;
+ }
+
+ fp_t const * const e_arr[1] = { e };
+
+ return ts_test(ts_market_facilitation_index, ts_market_facilitation_index_offset, x, e_arr, 5, (ts_constant_t *)NULL, &ts_market_facilitation_index_p);
+}
diff --git a/test/mass_index.c b/test/mass_index.c
new file mode 100644
index 0000000..2075b59
--- /dev/null
+++ b/test/mass_index.c
@@ -0,0 +1,26 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_mass_index( void )
+{
+ /* Use constant H-L so ratio converges to 1, sum = 26 */
+ fp_t o[30] = { 0 };
+ fp_t h[30];
+ fp_t l[30];
+ fp_t c[30] = { 0 };
+ fp_t v[30] = { 0 };
+
+ for ( uint_t i = 0; i < 30; ++i )
+ {
+ h[i] = 11;
+ l[i] = 9;
+ }
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t const e[4] = { 26.0, 26.0, 26.0, 26.0 };
+ fp_t const * const exp[1] = { e };
+
+ return ts_test(ts_mass_index, ts_mass_index_offset, x, exp, 30, (ts_constant_t *)NULL, &ts_mass_index_p);
+}
diff --git a/test/maximum.c b/test/maximum.c
new file mode 100644
index 0000000..585f30b
--- /dev/null
+++ b/test/maximum.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_maximum( void )
+{
+ uint_t const c[] = { 3 };
+
+ fp_t const x0[] = { 1, 3, 2, 5, 4, 1 };
+ fp_t const e0[] = { 3, 5, 5, 5 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_maximum, ts_maximum_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_maximum_p);
+}
diff --git a/test/median_price.c b/test/median_price.c
new file mode 100644
index 0000000..3cdb1cc
--- /dev/null
+++ b/test/median_price.c
@@ -0,0 +1,17 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_median_price( void )
+{
+ fp_t const x0[] = { 1, 1, 1, 1, 1 };
+ fp_t const x1[] = { 2, 4, 6, 8, 10 };
+ fp_t const x2[] = { 0, 2, 4, 6, 8 };
+ fp_t const x3[] = { 1, 1, 1, 1, 1 };
+ fp_t const x4[] = { 1, 1, 1, 1, 1 };
+ fp_t const e0[] = { 1, 3, 5, 7, 9 };
+
+ fp_t const * const x[] = { x0, x1, x2, x3, x4 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_median_price, ts_median_price_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_median_price_p);
+}
diff --git a/test/mesa_sine_wave.c b/test/mesa_sine_wave.c
new file mode 100644
index 0000000..598de80
--- /dev/null
+++ b/test/mesa_sine_wave.c
@@ -0,0 +1,22 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_mesa_sine_wave( void )
+{
+ /* k=3 */
+ fp_t const x[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+
+ fp_t const * const inp[1] = { x };
+
+ /* SMA(3): 11, 12, 13, 14, 15, 16, 17, 18 */
+ /* ROC: 13-11=2, 14-12=2, 15-13=2, 16-14=2, 17-15=2, 18-16=2, 19-18=2 */
+ /* EMA(5) of constant 2 converges to 2, clamped to 1 */
+ /* All sines = 1, all leads = 0 */
+ fp_t const es[] = { 1, 1, 1, 1, 1, 1, 1 };
+ fp_t const el[] = { 0, 0, 0, 0, 0, 0, 0 };
+ fp_t const * const exp[2] = { es, el };
+
+ uint_t k = 3;
+ return ts_test(ts_mesa_sine_wave, ts_mesa_sine_wave_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_mesa_sine_wave_p);
+}
diff --git a/test/mfi.c b/test/mfi.c
new file mode 100644
index 0000000..1d83db4
--- /dev/null
+++ b/test/mfi.c
@@ -0,0 +1,59 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_mfi( void )
+{
+ uint_t k = 3;
+
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 };
+ fp_t const c[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+ fp_t const v[] = { 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t tp[10] = { 0 };
+ for( uint_t i = 0; i < 10; ++i )
+ {
+ tp[i] = ( h[i] + l[i] + c[i] ) / 3.0;
+ }
+
+ fp_t pos_sum = 0;
+ fp_t neg_sum = 0;
+
+ for( uint_t i = 1; i <= k; ++i )
+ {
+ fp_t raw_mf = tp[i] * v[i];
+ if( tp[i] > tp[i - 1] )
+ {
+ pos_sum += raw_mf;
+ }
+ else
+ {
+ neg_sum += raw_mf;
+ }
+ }
+
+ fp_t e[10] = { 0 };
+ e[0] = ( neg_sum == 0 ) ? 100.0 : 100.0 - 100.0 / ( 1.0 + pos_sum / neg_sum );
+
+ for( uint_t i = k + 1; i < 10; ++i )
+ {
+ fp_t cur_raw_mf = tp[i] * v[i];
+ if( tp[i] > tp[i - 1] ) pos_sum += cur_raw_mf;
+ else neg_sum += cur_raw_mf;
+
+ fp_t old_raw_mf = tp[i - k] * v[i - k];
+ if( tp[i - k] > tp[i - k - 1] ) pos_sum -= old_raw_mf;
+ else neg_sum -= old_raw_mf;
+
+ e[i - k] = ( neg_sum == 0 ) ? 100.0 : 100.0 - 100.0 / ( 1.0 + pos_sum / neg_sum );
+ }
+
+ fp_t const * const e_arr[1] = { e };
+
+ uint_t c_val = k;
+
+ return ts_test(ts_mfi, ts_mfi_offset, x, e_arr, 10, (ts_constant_t *)&c_val, &ts_mfi_p);
+}
diff --git a/test/midpoint.c b/test/midpoint.c
new file mode 100644
index 0000000..8048285
--- /dev/null
+++ b/test/midpoint.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_midpoint( void )
+{
+ fp_t const x0[] = { 1, 3, 2, 5, 4, 6, 3 };
+ fp_t const e0[] = { 2, 3.5, 3.5, 5, 4.5 };
+
+ fp_t const * const x[] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ uint_t const k = 3;
+
+ return ts_test(ts_midpoint, ts_midpoint_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&k, &ts_midpoint_p);
+}
diff --git a/test/midprice.c b/test/midprice.c
new file mode 100644
index 0000000..f1634b0
--- /dev/null
+++ b/test/midprice.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_midprice( void )
+{
+ fp_t const x0[] = { 10, 12, 11, 14, 13, 15, 12 };
+ fp_t const e0[] = { 11, 12.5, 12.5, 14, 13.5 };
+
+ fp_t const * const x[] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ uint_t const k = 3;
+
+ return ts_test(ts_midprice, ts_midprice_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&k, &ts_midprice_p);
+}
diff --git a/test/minimum.c b/test/minimum.c
new file mode 100644
index 0000000..2d4089d
--- /dev/null
+++ b/test/minimum.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_minimum( void )
+{
+ uint_t const c[] = { 3 };
+
+ fp_t const x0[] = { 1, 3, 2, 5, 4, 1 };
+ fp_t const e0[] = { 1, 2, 2, 1 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_minimum, ts_minimum_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_minimum_p);
+}
diff --git a/test/minmax.c b/test/minmax.c
new file mode 100644
index 0000000..64ba78b
--- /dev/null
+++ b/test/minmax.c
@@ -0,0 +1,17 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_minmax( void )
+{
+ uint_t k = 3;
+
+ fp_t const x[] = { 1, 3, 2, 5, 4, 1, 6, 3, 2, 4 };
+ fp_t const emin[] = { 1, 2, 2, 1, 1, 1, 2, 2 };
+ fp_t const emax[] = { 3, 5, 5, 5, 6, 6, 6, 4 };
+
+ fp_t const * const inp[1] = { x };
+ fp_t const * const exp[2] = { emin, emax };
+
+ return ts_test(ts_minmax, ts_minmax_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_minmax_p);
+}
diff --git a/test/missing_bar_count.c b/test/missing_bar_count.c
new file mode 100644
index 0000000..e15e634
--- /dev/null
+++ b/test/missing_bar_count.c
@@ -0,0 +1,28 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_missing_bar_count( void )
+{
+ fp_t threshold = 0.5;
+
+ fp_t const x[] = { 10, 10.1, 10.2, 10.1, 11, 11.1, 11.2, 11.3, 12.5, 12.6 };
+
+ fp_t const * const inp[1] = { x };
+
+ /* i=0: 0
+ * i=1: |10.1-10|=0.1 < 0.5, count=1
+ * i=2: |10.2-10.1|=0.1 < 0.5, count=2
+ * i=3: |10.1-10.2|=0.1 < 0.5, count=3
+ * i=4: |11-10.1|=0.9 >= 0.5, count=0
+ * i=5: |11.1-11|=0.1 < 0.5, count=1
+ * i=6: |11.2-11.1|=0.1 < 0.5, count=2
+ * i=7: |11.3-11.2|=0.1 < 0.5, count=3
+ * i=8: |12.5-11.3|=1.2 >= 0.5, count=0
+ * i=9: |12.6-12.5|=0.1 < 0.5, count=1
+ */
+ fp_t const e[] = { 0, 1, 2, 3, 0, 1, 2, 3, 0, 1 };
+ fp_t const * const exp[1] = { e };
+
+ return ts_test(ts_missing_bar_count, ts_missing_bar_count_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&threshold, &ts_missing_bar_count_p);
+}
diff --git a/test/momentum.c b/test/momentum.c
new file mode 100644
index 0000000..78772dc
--- /dev/null
+++ b/test/momentum.c
@@ -0,0 +1,19 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_momentum( void )
+{
+ /* k=3, close: 10, 12, 14, 16, 18, 20 */
+ /* i=3: 16-10 = 6 */
+ /* i=4: 18-12 = 6 */
+ /* i=5: 20-14 = 6 */
+ fp_t const x[] = { 10, 12, 14, 16, 18, 20 };
+
+ fp_t const * const inp[1] = { x };
+ fp_t const e[] = { 6, 6, 6 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_momentum, ts_momentum_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_momentum_p);
+}
diff --git a/test/money_flow_multiplier.c b/test/money_flow_multiplier.c
new file mode 100644
index 0000000..58ca4c9
--- /dev/null
+++ b/test/money_flow_multiplier.c
@@ -0,0 +1,17 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_money_flow_multiplier( void )
+{
+ fp_t const o[] = { 9, 10, 11, 12, 13 };
+ fp_t const h[] = { 10, 11, 12, 13, 14 };
+ fp_t const l[] = { 8, 9, 10, 11, 12 };
+ fp_t const c[] = { 9.5, 10.5, 11.5, 12.5, 13.5 };
+ fp_t const v[] = { 100, 200, 300, 400, 500 };
+ fp_t const e0[] = { 0.5, 0.5, 0.5, 0.5, 0.5 };
+
+ fp_t const * const x[5] = { o, h, l, c, v };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_money_flow_multiplier, ts_money_flow_multiplier_offset, x, e, sizeof( o ) / sizeof( *o ), (ts_constant_t *)NULL, &ts_money_flow_multiplier_p);
+}
diff --git a/test/moving_linear_regression.c b/test/moving_linear_regression.c
new file mode 100644
index 0000000..dd7add0
--- /dev/null
+++ b/test/moving_linear_regression.c
@@ -0,0 +1,15 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_moving_linear_regression( void )
+{
+ fp_t const x[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+ fp_t const * const inp[1] = { x };
+
+ fp_t const e[] = { 5, 6, 7, 8 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 5;
+ return ts_test(ts_moving_linear_regression, ts_moving_linear_regression_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_moving_linear_regression_p);
+}
diff --git a/test/moving_linear_regression_r2.c b/test/moving_linear_regression_r2.c
new file mode 100644
index 0000000..9013584
--- /dev/null
+++ b/test/moving_linear_regression_r2.c
@@ -0,0 +1,15 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_moving_linear_regression_r2( void )
+{
+ fp_t const x[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+ fp_t const * const inp[1] = { x };
+
+ fp_t const e[] = { 1.0, 1.0, 1.0, 1.0 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 5;
+ return ts_test(ts_moving_linear_regression_r2, ts_moving_linear_regression_r2_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_moving_linear_regression_r2_p);
+}
diff --git a/test/multiplication.c b/test/multiplication.c
new file mode 100644
index 0000000..ad61fb0
--- /dev/null
+++ b/test/multiplication.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_multiplication( void )
+{
+ fp_t const x0[] = { 3, 5, 7 };
+ fp_t const x1[] = { 2, 4, 6 };
+ fp_t const e0[] = { 6, 20, 42 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_multiplication, ts_multiplication_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_multiplication_p);
+}
diff --git a/test/nan_fn.c b/test/nan_fn.c
new file mode 100644
index 0000000..e0f29ec
--- /dev/null
+++ b/test/nan_fn.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_nan_fn( void )
+{
+ fp_t const x0[] = { 1, 2, 3 };
+ fp_t out[3];
+ ts_nan_fn( (fp_t*[]){out}, 1, (fp_t const *[]){x0}, 1, 3, NULL );
+ /* Verify all outputs are NaN */
+ for ( uint_t i = 0; i < 3; ++i )
+ if ( out[i] == out[i] ) return false; /* not NaN */
+ return true;
+}
diff --git a/test/natr.c b/test/natr.c
new file mode 100644
index 0000000..614754f
--- /dev/null
+++ b/test/natr.c
@@ -0,0 +1,22 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_natr( void )
+{
+ fp_t const o[] = { 9, 11, 10, 12, 12, 13, 11, 14, 13, 15 };
+ fp_t const h[] = { 10, 12, 11, 13, 12, 14, 12, 15, 14, 16 };
+ fp_t const l[] = { 8, 9, 10, 11, 10, 12, 11, 13, 12, 14 };
+ fp_t const c[] = { 9, 10, 12, 11, 13, 11, 14, 12, 15, 13 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ /* ATR values: 2.0, 1.66667, 2.0, 1.66667, 1.66667, 1.66667, 2.0 */
+ /* C at idx 3-9: 11, 13, 11, 14, 12, 15, 13 */
+ /* NATR = ATR/C*100 */
+ fp_t const e[] = { 18.18182, 12.82051, 18.18182, 11.90476, 13.88889, 11.11111, 15.38462 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_natr, ts_natr_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_natr_p);
+}
diff --git a/test/nearbyint.c b/test/nearbyint.c
new file mode 100644
index 0000000..b17e10c
--- /dev/null
+++ b/test/nearbyint.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_nearbyint( void )
+{
+ fp_t const x0[] = { -2.700000, -1.300000, 0.300000, 0.700000, 1.500000, 2.700000 };
+ fp_t const e0[] = { -3.000000, -1.000000, 0.000000, 1.000000, 2.000000, 3.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_nearbyint, ts_nearbyint_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_nearbyint_p);
+}
diff --git a/test/nextafter.c b/test/nextafter.c
new file mode 100644
index 0000000..c7e882b
--- /dev/null
+++ b/test/nextafter.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_nextafter( void )
+{
+ fp_t const x0[] = { 1.000000, 1.000000, 0.000000 };
+ fp_t const x1[] = { 2.000000, 0.000000, 1.000000 };
+ fp_t const e0[] = { 1.000000, 1.000000, 0.000000 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_nextafter, ts_nextafter_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_nextafter_p);
+}
diff --git a/test/nexttoward.c b/test/nexttoward.c
new file mode 100644
index 0000000..32d41cc
--- /dev/null
+++ b/test/nexttoward.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_nexttoward( void )
+{
+ fp_t const x0[] = { 1.000000, 1.000000, 0.000000 };
+ fp_t const x1[] = { 2.000000, 0.000000, 1.000000 };
+ fp_t const e0[] = { 1.000000, 1.000000, 0.000000 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_nexttoward, ts_nexttoward_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_nexttoward_p);
+}
diff --git a/test/none.c b/test/none.c
new file mode 100644
index 0000000..80d9f9e
--- /dev/null
+++ b/test/none.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_none( void )
+{
+ fp_t const x0[] = { 1, 2, 3, 4, 5 };
+ fp_t const e0[] = { 1, 2, 3, 4, 5 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_none, ts_none_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_none_p);
+}
diff --git a/test/nvi.c b/test/nvi.c
new file mode 100644
index 0000000..87b3ddb
--- /dev/null
+++ b/test/nvi.c
@@ -0,0 +1,38 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_nvi( void )
+{
+ fp_t const o[] = { 10, 11, 12, 11, 12 };
+ fp_t const h[] = { 12, 13, 14, 12, 14 };
+ fp_t const l[] = { 8, 9, 10, 9, 10 };
+ fp_t const c[] = { 11, 12, 10, 11, 11 };
+ fp_t const v[] = { 100, 200, 300, 400, 500 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t nvi = 1000;
+
+ nvi = 1000;
+ fp_t e[5] = { nvi };
+
+ e[1] = nvi;
+ if( v[1] < v[0] ) { fp_t r = (c[1] - c[0]) / c[0]; nvi = nvi * (1 + r); e[1] = nvi; }
+ else { e[1] = nvi; }
+
+ if( v[2] < v[1] ) { fp_t r = (c[2] - c[1]) / c[1]; nvi = nvi * (1 + r); }
+ else { /* unchanged */ }
+ e[2] = nvi;
+
+ if( v[3] < v[2] ) { fp_t r = (c[3] - c[2]) / c[2]; nvi = nvi * (1 + r); }
+ else { /* unchanged */ }
+ e[3] = nvi;
+
+ if( v[4] < v[3] ) { fp_t r = (c[4] - c[3]) / c[3]; nvi = nvi * (1 + r); }
+ else { /* unchanged */ }
+ e[4] = nvi;
+
+ fp_t const * const e_arr[1] = { e };
+
+ return ts_test(ts_nvi, ts_nvi_offset, x, e_arr, 5, (ts_constant_t *)NULL, &ts_nvi_p);
+}
diff --git a/test/obv.c b/test/obv.c
new file mode 100644
index 0000000..112740e
--- /dev/null
+++ b/test/obv.c
@@ -0,0 +1,18 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_obv( void )
+{
+ fp_t const o[] = { 10, 11, 12, 11, 12 };
+ fp_t const h[] = { 12, 13, 14, 12, 14 };
+ fp_t const l[] = { 8, 9, 10, 9, 10 };
+ fp_t const c[] = { 11, 12, 10, 11, 11 };
+ fp_t const v[] = { 100, 200, 300, 400, 500 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t const e[] = { 200, -100, 300, 300 };
+ fp_t const * const e_arr[1] = { e };
+
+ return ts_test(ts_obv, ts_obv_offset, x, e_arr, 5, (ts_constant_t *)NULL, &ts_obv_p);
+}
diff --git a/test/on_balance_volume_rate.c b/test/on_balance_volume_rate.c
new file mode 100644
index 0000000..65e3ac1
--- /dev/null
+++ b/test/on_balance_volume_rate.c
@@ -0,0 +1,45 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_on_balance_volume_rate( void )
+{
+ uint_t k = TS_TEST_K;
+
+ fp_t const o[] = { 10, 11, 12, 11, 12, 10, 11, 13, 12, 14 };
+ fp_t const h[] = { 12, 13, 14, 12, 14, 11, 12, 15, 14, 16 };
+ fp_t const l[] = { 8, 9, 10, 9, 10, 9, 10, 11, 10, 12 };
+ fp_t const c[] = { 11, 12, 10, 11, 11, 10, 12, 14, 13, 15 };
+ fp_t const v[] = { 100, 200, 300, 400, 500, 300, 200, 400, 300, 500 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t obv_buf[10] = { 0 };
+ fp_t obv = 0;
+
+ for( uint_t i = 1; i < 10; ++i )
+ {
+ if( c[i] > c[i-1] )
+ {
+ obv += v[i];
+ }
+ else if( c[i] < c[i-1] )
+ {
+ obv -= v[i];
+ }
+ obv_buf[i] = obv;
+ }
+
+ fp_t e[4] = { 0 };
+
+ for( uint_t i = k + 1; i < 10; ++i )
+ {
+ fp_t obv_prev = obv_buf[i - k];
+ e[i - (k + 1)] = obv_prev != 0 ? ( obv_buf[i] - obv_prev ) / obv_prev * 100 : 0;
+ }
+
+ fp_t const * const e_arr[1] = { e };
+
+ fp_t c_val = k;
+
+ return ts_test(ts_on_balance_volume_rate, ts_on_balance_volume_rate_offset, x, e_arr, 10, (ts_constant_t *)&c_val, &ts_on_balance_volume_rate_p);
+}
diff --git a/test/open_interest.c b/test/open_interest.c
new file mode 100644
index 0000000..165c3c5
--- /dev/null
+++ b/test/open_interest.c
@@ -0,0 +1,14 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_open_interest( void )
+{
+ fp_t const x[] = { 100, 200, 300, 400, 500 };
+ fp_t const e[] = { 100, 200, 300, 400, 500 };
+
+ fp_t const * const inp[1] = { x };
+ fp_t const * const exp[1] = { e };
+
+ return ts_test(ts_open_interest, ts_open_interest_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)NULL, &ts_open_interest_p);
+}
diff --git a/test/open_minus_close.c b/test/open_minus_close.c
new file mode 100644
index 0000000..329b678
--- /dev/null
+++ b/test/open_minus_close.c
@@ -0,0 +1,17 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_open_minus_close( void )
+{
+ fp_t const x0[] = { 5, 10, 15, 20, 25 };
+ fp_t const x1[] = { 6, 11, 16, 21, 26 };
+ fp_t const x2[] = { 0, 5, 10, 15, 20 };
+ fp_t const x3[] = { 3, 8, 13, 18, 23 };
+ fp_t const x4[] = { 1, 1, 1, 1, 1 };
+ fp_t const e0[] = { 2, 2, 2, 2, 2 };
+
+ fp_t const * const x[] = { x0, x1, x2, x3, x4 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_open_minus_close, ts_open_minus_close_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_open_minus_close_p);
+}
diff --git a/test/oscillator_cross.c b/test/oscillator_cross.c
new file mode 100644
index 0000000..323718b
--- /dev/null
+++ b/test/oscillator_cross.c
@@ -0,0 +1,40 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_oscillator_cross( void )
+{
+ /* Data that goes down then up, producing a clear crossover */
+ fp_t const x[] = { 10, 9, 8, 7, 6, 5, 6, 7, 8, 9 };
+
+ fp_t const * const inp[1] = { x };
+
+ /* fast=2, slow=4 */
+ fp_t fast_e = 10, slow_e = 10;
+ fp_t fast_a = 1.0 / 2, slow_a = 1.0 / 4;
+ fp_t e[6] = { 0 };
+
+ for( uint_t i = 1; i < 10; ++i )
+ {
+ fp_t pf = fast_e, ps = slow_e;
+ fast_e = fast_e + fast_a * ( x[i] - fast_e );
+ slow_e = slow_e + slow_a * ( x[i] - slow_e );
+
+ if( i >= 4 )
+ {
+ if( fast_e > slow_e && pf <= ps )
+ e[i - 4] = 1;
+ else if( fast_e < slow_e && pf >= ps )
+ e[i - 4] = -1;
+ else
+ e[i - 4] = 0;
+ }
+ }
+
+ fp_t const * const exp[1] = { e };
+
+ typedef struct { fp_t fast; fp_t slow; } c_t;
+ c_t c = { .fast = 2, .slow = 4 };
+
+ return ts_test(ts_oscillator_cross, ts_oscillator_cross_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&c, &ts_oscillator_cross_p);
+}
diff --git a/test/osma.c b/test/osma.c
new file mode 100644
index 0000000..5a94bb9
--- /dev/null
+++ b/test/osma.c
@@ -0,0 +1,20 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_osma( void )
+{
+ fp_t const x[] = { -10, -7.5, -5, -2.5, 0, 2.5, 5, 7.5, 10 };
+
+ fp_t const * const inp[1] = { x };
+
+ /* fast=3, slow=5, signal=2 */
+ fp_t const e[4] = { 0.24499095, 0.34043610, 0.35920247, 0.34054338 };
+
+ fp_t const * const exp[1] = { e };
+
+ typedef struct { fp_t fast; fp_t slow; fp_t signal; } c_t;
+ c_t c = { .fast = 3, .slow = 5, .signal = 2 };
+
+ return ts_test(ts_osma, ts_osma_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&c, &ts_osma_p);
+}
diff --git a/test/parabolic_sar.c b/test/parabolic_sar.c
new file mode 100644
index 0000000..ffee1bf
--- /dev/null
+++ b/test/parabolic_sar.c
@@ -0,0 +1,22 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_parabolic_sar( void )
+{
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
+ fp_t const c[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ /* step=0.02, max=0.2, uptrend throughout */
+ fp_t const esar[] = { 9.000000, 9.000000, 9.060000, 9.217600, 9.504544, 9.944180, 10.549762, 11.323791, 12.258460, 13.337107 };
+ fp_t const edir[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
+ fp_t const * const exp[2] = { esar, edir };
+
+ typedef struct { fp_t step; fp_t max_af; } c_t;
+ c_t consts = { .step = 0.02, .max_af = 0.2 };
+ return ts_test(ts_parabolic_sar, ts_parabolic_sar_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&consts, &ts_parabolic_sar_p);
+}
diff --git a/test/performance_index.c b/test/performance_index.c
new file mode 100644
index 0000000..e31ec98
--- /dev/null
+++ b/test/performance_index.c
@@ -0,0 +1,16 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_performance_index( void )
+{
+ fp_t const x0[] = { 10, 20, 30, 40, 50 };
+ fp_t const x1[] = { 11, 22, 33, 44, 55 };
+ fp_t const x2[] = { 9, 18, 28, 38, 48 };
+ fp_t const x3[] = { 12, 21, 31, 41, 51 };
+ fp_t const e0[] = { 20, 5, 3.333333, 2.5, 2 };
+
+ fp_t const * const x[] = { x0, x1, x2, x3 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_performance_index, ts_performance_index_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_performance_index_p);
+}
diff --git a/test/performance_ratio.c b/test/performance_ratio.c
new file mode 100644
index 0000000..74ff1bd
--- /dev/null
+++ b/test/performance_ratio.c
@@ -0,0 +1,17 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_performance_ratio( void )
+{
+ uint_t k = 3;
+
+ fp_t const x[] = { 100, 101, 102, 103, 104, 105, 106, 107, 108, 109 };
+
+ fp_t const * const inp[1] = { x };
+
+ fp_t const e[] = { 123.693169, 124.917973, 126.142776, 127.367579, 128.592379, 129.817179, 131.041978 };
+ fp_t const * const exp[1] = { e };
+
+ return ts_test(ts_performance_ratio, ts_performance_ratio_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_performance_ratio_p);
+}
diff --git a/test/pivot_points.c b/test/pivot_points.c
new file mode 100644
index 0000000..08138b6
--- /dev/null
+++ b/test/pivot_points.c
@@ -0,0 +1,27 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_pivot_points( void )
+{
+ fp_t const o[] = { 10, 12, 11, 14, 13, 15, 16, 14, 17, 18 };
+ fp_t const h[] = { 12, 14, 13, 16, 15, 17, 18, 16, 19, 20 };
+ fp_t const l[] = { 9, 11, 10, 13, 12, 14, 15, 13, 16, 17 };
+ fp_t const c[] = { 11, 13, 12, 15, 14, 16, 17, 15, 18, 19 };
+ fp_t const v[] = { 100, 200, 150, 180, 160, 190, 210, 170, 200, 220 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ /* Using prev bar OHLC */
+ /* i=1: prev=bar0, P=(12+9+11)/3=10.667, R1=2*10.667-9=12.333, S1=2*10.667-12=9.333, R2=10.667+3=13.667, S2=10.667-3=7.667 */
+ /* i=2: prev=bar1, P=(14+11+13)/3=12.667, R1=2*12.667-11=14.333, S1=2*12.667-14=11.333, R2=12.667+3=15.667, S2=12.667-3=9.667 */
+ fp_t const ep[] = { 10.666667, 12.666667, 11.666667, 14.666667, 13.666667, 15.666667, 16.666667, 14.666667, 17.666667 };
+ fp_t const er1[] = { 12.333333, 14.333333, 13.333333, 16.333333, 15.333333, 17.333333, 18.333333, 16.333333, 19.333333 };
+ fp_t const es1[] = { 9.333333, 11.333333, 10.333333, 13.333333, 12.333333, 14.333333, 15.333333, 13.333333, 16.333333 };
+ fp_t const er2[] = { 13.666667, 15.666667, 14.666667, 17.666667, 16.666667, 18.666667, 19.666667, 17.666667, 20.666667 };
+ fp_t const es2[] = { 7.666667, 9.666667, 8.666667, 11.666667, 10.666667, 12.666667, 13.666667, 11.666667, 14.666667 };
+ fp_t const * const exp[5] = { ep, er1, es1, er2, es2 };
+
+ return ts_test(ts_pivot_points, ts_pivot_points_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)NULL, &ts_pivot_points_p);
+}
+
diff --git a/test/plot.c b/test/plot.c
new file mode 100644
index 0000000..f697af7
--- /dev/null
+++ b/test/plot.c
@@ -0,0 +1,14 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_plot( void )
+{
+ fp_t const x[] = { 1, 2, 3, 4, 5 };
+ fp_t const e[] = { 1, 2, 3, 4, 5 };
+
+ fp_t const * const inp[1] = { x };
+ fp_t const * const exp[1] = { e };
+
+ return ts_test(ts_plot, ts_plot_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)NULL, &ts_plot_p);
+}
diff --git a/test/poisson_pdf.c b/test/poisson_pdf.c
new file mode 100644
index 0000000..0dfc9f2
--- /dev/null
+++ b/test/poisson_pdf.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_poisson_pdf( void )
+{
+ fp_t const c[] = { 3.000000 };
+
+ fp_t const x0[] = { 0.000000, 1.000000, 2.000000, 3.000000, 4.000000, 5.000000 };
+ fp_t const e0[] = { 0.049787, 0.149361, 0.224042, 0.224042, 0.168031, 0.100819 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_poisson_pdf, ts_poisson_pdf_offset, x, e, sizeof( c ) / sizeof( *c ), (ts_constant_t *)&c, &ts_poisson_pdf_p);
+}
diff --git a/test/polynomial.c b/test/polynomial.c
new file mode 100644
index 0000000..1591343
--- /dev/null
+++ b/test/polynomial.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_polynomial( void )
+{
+ /* y = x0 ^ x1 */
+ fp_t const x0[] = { 1, 2, 3, 4, 5 };
+ fp_t const x1[] = { 0, 1, 2, 3, 4 };
+ fp_t const e0[] = { 1, 2, 9, 64, 625 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_polynomial, ts_polynomial_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), NULL, &ts_polynomial_p);
+}
diff --git a/test/power.c b/test/power.c
new file mode 100644
index 0000000..07bf08f
--- /dev/null
+++ b/test/power.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_power( void )
+{
+ fp_t const c[] = { 2.0 };
+
+ fp_t const x0[] = { 0, 1, 2, 3, 4 };
+ fp_t const e0[] = { 0, 1, 4, 9, 16 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_power, ts_power_offset, x, e, sizeof( c ) / sizeof( *c ), (ts_constant_t *)&c, &ts_power_p);
+}
diff --git a/test/ppo.c b/test/ppo.c
new file mode 100644
index 0000000..c513567
--- /dev/null
+++ b/test/ppo.c
@@ -0,0 +1,21 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_ppo( void )
+{
+ fp_t const x[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+
+ fp_t const * const inp[1] = { x };
+
+ /* fast=3, slow=5, signal=2 */
+ fp_t const ep[5] = { 7.73841405, 8.63701135, 9.23626984, 9.58993759, 9.75198730 };
+ fp_t const es[5] = { 7.11991901, 7.87846518, 8.55736751, 9.07365255, 9.41281993 };
+
+ fp_t const * const exp[2] = { ep, es };
+
+ typedef struct { fp_t fast; fp_t slow; fp_t signal; } c_t;
+ c_t c = { .fast = 3, .slow = 5, .signal = 2 };
+
+ return ts_test(ts_ppo, ts_ppo_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&c, &ts_ppo_p);
+}
diff --git a/test/pretty_good_oscillator.c b/test/pretty_good_oscillator.c
new file mode 100644
index 0000000..320c816
--- /dev/null
+++ b/test/pretty_good_oscillator.c
@@ -0,0 +1,27 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_pretty_good_oscillator( void )
+{
+ fp_t const x[] = { 95, 100, 105, 110, 102, 115, 120, 112, 125, 130, 122, 135 };
+ fp_t const * const inp[1] = { x };
+
+ fp_t const e[] = {
+ 0.0000000000,
+ 0.0511756570,
+ 0.1007296091,
+ 0.1485166224,
+ 0.0631698925,
+ 0.1924376246,
+ 0.2363349679,
+ 0.1492577606,
+ 0.2731834852,
+ 0.3129126874,
+ 0.2246375214,
+ 0.3425844656
+ };
+ fp_t const * const exp[1] = { e };
+
+ return ts_test(ts_pretty_good_oscillator, ts_pretty_good_oscillator_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)NULL, &ts_pretty_good_oscillator_p);
+}
diff --git a/test/price_channel.c b/test/price_channel.c
new file mode 100644
index 0000000..ba16e69
--- /dev/null
+++ b/test/price_channel.c
@@ -0,0 +1,21 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_price_channel( void )
+{
+ fp_t const x[] = { 10, 12, 11, 14, 13, 15, 16, 14, 17, 18 };
+ fp_t const * const inp[1] = { x };
+
+ /* k=3, offset=2, y_n=8 */
+ /* HH3: 12,14,14,15,16,16,17,18 */
+ /* LL3: 10,11,11,13,13,14,14,14 */
+ /* SMA3: 11,12.333,12.667,14,14.667,15,15.667,16.333 */
+ fp_t const eu[] = { 12, 14, 14, 15, 16, 16, 17, 18 };
+ fp_t const em[] = { 11, 12.33333, 12.66667, 14, 14.66667, 15, 15.66667, 16.33333 };
+ fp_t const el[] = { 10, 11, 11, 13, 13, 14, 14, 14 };
+ fp_t const * const exp[3] = { eu, em, el };
+
+ uint_t k = 3;
+ return ts_test(ts_price_channel, ts_price_channel_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_price_channel_p);
+}
diff --git a/test/price_momentum_oscillator.c b/test/price_momentum_oscillator.c
new file mode 100644
index 0000000..31b7724
--- /dev/null
+++ b/test/price_momentum_oscillator.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_price_momentum_oscillator( void )
+{
+ fp_t const x0[] = { 100, 110, 120, 130, 140, 150, 160 };
+ fp_t const e0[] = { 20, 18.181818, 16.666667, 15.384615, 14.285714 };
+
+ fp_t const * const x[] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ uint_t k = 2;
+
+ return ts_test(ts_price_momentum_oscillator, ts_price_momentum_oscillator_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&k, &ts_price_momentum_oscillator_p);
+}
diff --git a/test/price_rate_of_change.c b/test/price_rate_of_change.c
new file mode 100644
index 0000000..ec3eb9c
--- /dev/null
+++ b/test/price_rate_of_change.c
@@ -0,0 +1,27 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_price_rate_of_change( void )
+{
+ fp_t const x[] = { 100, 110, 120, 130, 140, 150, 160, 170, 180, 190 };
+
+ fp_t const * const inp[1] = { x };
+
+ /* k=3 */
+ /* PROC[3] = (130-100)/100*100 = 30 */
+ /* PROC[4] = (140-110)/110*100 = 27.2727 */
+ /* PROC[5] = (150-120)/120*100 = 25 */
+ /* PROC[6] = (160-130)/130*100 = 23.0769 */
+ /* PROC[7] = (170-140)/140*100 = 21.4286 */
+ /* PROC[8] = (180-150)/150*100 = 20 */
+ /* PROC[9] = (190-160)/160*100 = 18.75 */
+
+ fp_t e[] = { 30.0, 27.272727272727273, 25.0, 23.076923076923077, 21.428571428571427, 20.0, 18.75 };
+
+ fp_t const * const exp[1] = { e };
+
+ uint_t c_val = 3;
+
+ return ts_test(ts_price_rate_of_change, ts_price_rate_of_change_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&c_val, &ts_price_rate_of_change_p);
+}
diff --git a/test/prime_number_bands.c b/test/prime_number_bands.c
new file mode 100644
index 0000000..6d0b211
--- /dev/null
+++ b/test/prime_number_bands.c
@@ -0,0 +1,17 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_prime_number_bands( void )
+{
+ uint_t const c = 3;
+
+ fp_t const x0[] = { 1, 2, 3, 4, 5 };
+ fp_t const e0[] = { 2, 3, 4 };
+ fp_t const e1[] = { 3.632993, 4.632993, 5.632993 };
+ fp_t const e2[] = { 0.367007, 1.367007, 2.367007 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[3] = { e0, e1, e2 };
+
+ return ts_test(ts_prime_number_bands, ts_prime_number_bands_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_prime_number_bands_p);
+}
diff --git a/test/prime_number_oscillator.c b/test/prime_number_oscillator.c
new file mode 100644
index 0000000..aa6b575
--- /dev/null
+++ b/test/prime_number_oscillator.c
@@ -0,0 +1,23 @@
+#include <stddef.h>
+#include <stdlib.h>
+#include "ts.h"
+
+bool ts_test_prime_number_oscillator( void )
+{
+ uint_t n = 25;
+ fp_t *x0 = malloc( n * sizeof( fp_t ) );
+ if ( !x0 ) return false;
+
+ for ( uint_t i = 0; i < n; ++i )
+ x0[i] = (fp_t)(10 + i);
+
+ fp_t const * const inp[1] = { x0 };
+
+ fp_t const e[] = { 2, 2 };
+ fp_t const * const exp[1] = { e };
+
+ bool result = ts_test(ts_prime_number_oscillator, ts_prime_number_oscillator_offset, inp, exp, n, (ts_constant_t *)NULL, &ts_prime_number_oscillator_p);
+
+ free( x0 );
+ return result;
+}
diff --git a/test/prings_kst.c b/test/prings_kst.c
new file mode 100644
index 0000000..8a1bd28
--- /dev/null
+++ b/test/prings_kst.c
@@ -0,0 +1,55 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_prings_kst( void )
+{
+ fp_t const x[] = {
+ 100, 105, 98, 110, 102, 115, 108, 120, 112, 125,
+ 118, 130, 122, 135, 128, 140, 132, 145, 138, 150,
+ 142, 155, 148, 160, 152, 165, 158, 170, 162, 175,
+ 168, 180, 172, 185, 178, 190, 182, 195, 188, 200,
+ 192, 205, 198, 210, 202, 215, 208, 220, 212, 225,
+ 218, 230, 222, 235, 228, 240
+ };
+
+ fp_t const * const inp[1] = { x };
+
+ fp_t const e0[] = {
+ 51.9408578539013, 75.1308260917654, 78.103866719,
+ 94.8088438575893, 92.483818257572, 105.456228286493,
+ 101.394220195137, 111.288138429023, 104.640206278203,
+ 113.013133431368, 106.311034357386, 113.131203505727,
+ 105.038276407899, 111.304589974597, 103.842952160622,
+ 109.243663263608, 100.915323682376, 106.178344115008,
+ 98.7322514625897, 103.443195102946, 95.3595796482774,
+ 100.10652950864, 92.9706808772205, 97.3126510773803,
+ 89.6400548204848, 94.0861019609992, 87.3507958601018,
+ 91.4647371720775, 84.2384208045559, 88.4793019904416,
+ 82.1513111963106, 86.0996943377378, 79.3034333929425,
+ 83.3828886921002, 77.4376734462827, 81.2491274609335,
+ 74.8491800513007, 78.788567419848, 73.1922770738286,
+ 76.8810806798416, 70.8401912421373, 74.6513723261792,
+ 69.3696853171888, 72.9441719183728, 67.2269459044808,
+ 70.9178130955952, 65.9191399381158
+ };
+ fp_t const e1[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 90.5830006631871,
+ 93.3867672592096, 95.0023006464817, 97.2684135038873,
+ 98.2396463668887, 99.8727643178523, 100.369037798198,
+ 101.478365981375, 101.407985694, 102.004280496626,
+ 101.595276867371, 101.826266646818, 101.017930772,
+ 100.90400561408, 99.9123400219729, 99.5873789038989,
+ 98.3439633934721, 97.811730714413, 96.5041138576241,
+ 95.8741917719308, 94.4197204010089, 93.677168099688,
+ 92.2364359867658, 91.4693432806373, 89.9486045446755,
+ 89.1278900631035, 87.6666129860009, 86.8644272953675,
+ 85.3625213898592, 84.5407771436078, 83.1222146348854,
+ 82.3420728905049, 80.9043376844589, 80.122717014674,
+ 78.7785880524883, 78.0492860357239, 76.6964935193185,
+ 75.9741584663531, 74.7172811503234
+ };
+ fp_t const * const exp[2] = { e0, e1 };
+
+ return ts_test(ts_prings_kst, ts_prings_kst_offset, inp, exp, 55, (ts_constant_t *)NULL, &ts_prings_kst_p);
+}
diff --git a/test/prings_special_k.c b/test/prings_special_k.c
new file mode 100644
index 0000000..41e6a89
--- /dev/null
+++ b/test/prings_special_k.c
@@ -0,0 +1,23 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_prings_special_k( void )
+{
+ /* k1=3, k2=2 */
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
+ fp_t const c[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+/* Monotonically increasing: %K_raw = 80 for all windows */
+ fp_t const ek[] = { 80, 80, 80, 80, 80, 80, 80 };
+ fp_t const ed[] = { 80, 80, 80, 80, 80, 80, 80 };
+ fp_t const * const exp[2] = { ek, ed };
+
+ typedef struct { uint_t k1; uint_t k2; } c_t;
+ c_t constants = { .k1 = 3, .k2 = 2 };
+
+ return ts_test(ts_prings_special_k, ts_prings_special_k_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&constants, &ts_prings_special_k_p);
+}
diff --git a/test/psychological_line.c b/test/psychological_line.c
new file mode 100644
index 0000000..711923c
--- /dev/null
+++ b/test/psychological_line.c
@@ -0,0 +1,23 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_psychological_line( void )
+{
+ fp_t const x0[] = { 10, 10, 20, 20, 30 };
+ fp_t const x1[] = { 12, 11, 22, 19, 32 };
+ fp_t const x2[] = { 9, 9, 19, 18, 29 };
+ fp_t const x3[] = { 11, 10, 21, 20, 31 };
+
+ uint_t const k = 2;
+
+ fp_t e[4];
+ e[0] = 50;
+ e[1] = 50;
+ e[2] = 50;
+ e[3] = 50;
+
+ fp_t const * const x[] = { x0, x1, x2, x3 };
+ fp_t const * const exp[1] = { e };
+
+ return ts_test(ts_psychological_line, ts_psychological_line_offset, x, exp, 5, (ts_constant_t *)&k, &ts_psychological_line_p);
+}
diff --git a/test/pvi.c b/test/pvi.c
new file mode 100644
index 0000000..51b4e5c
--- /dev/null
+++ b/test/pvi.c
@@ -0,0 +1,34 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_pvi( void )
+{
+ fp_t const o[] = { 10, 11, 12, 11, 12 };
+ fp_t const h[] = { 12, 13, 14, 12, 14 };
+ fp_t const l[] = { 8, 9, 10, 9, 10 };
+ fp_t const c[] = { 11, 12, 10, 11, 11 };
+ fp_t const v[] = { 100, 200, 300, 400, 500 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t pvi = 1000;
+ fp_t full[5];
+ full[0] = pvi;
+
+ if( v[1] > v[0] ) { fp_t r = (c[1] - c[0]) / c[0]; pvi = pvi * (1 + r); }
+ full[1] = pvi;
+
+ if( v[2] > v[1] ) { fp_t r = (c[2] - c[1]) / c[1]; pvi = pvi * (1 + r); }
+ full[2] = pvi;
+
+ if( v[3] > v[2] ) { fp_t r = (c[3] - c[2]) / c[2]; pvi = pvi * (1 + r); }
+ full[3] = pvi;
+
+ if( v[4] > v[3] ) { fp_t r = (c[4] - c[3]) / c[3]; pvi = pvi * (1 + r); }
+ full[4] = pvi;
+
+ fp_t const e[] = { full[0], full[1], full[2], full[3], full[4] };
+ fp_t const * const e_arr[1] = { e };
+
+ return ts_test(ts_pvi, ts_pvi_offset, x, e_arr, 5, (ts_constant_t *)NULL, &ts_pvi_p);
+}
diff --git a/test/pvt.c b/test/pvt.c
new file mode 100644
index 0000000..6abccb4
--- /dev/null
+++ b/test/pvt.c
@@ -0,0 +1,27 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_pvt( void )
+{
+ fp_t const o[] = { 10, 11, 12, 11, 12 };
+ fp_t const h[] = { 12, 13, 14, 12, 14 };
+ fp_t const l[] = { 8, 9, 10, 9, 10 };
+ fp_t const c[] = { 11, 12, 10, 11, 11 };
+ fp_t const v[] = { 100, 200, 300, 400, 500 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t pvt = 0;
+ fp_t full[5] = { 0 };
+
+ for( uint_t i = 1; i < 5; ++i )
+ {
+ pvt += ( c[i] / c[i-1] - 1 ) * v[i];
+ full[i] = pvt;
+ }
+
+ fp_t const e[] = { full[1], full[2], full[3], full[4] };
+ fp_t const * const e_arr[1] = { e };
+
+ return ts_test(ts_pvt, ts_pvt_offset, x, e_arr, 5, (ts_constant_t *)NULL, &ts_pvt_p);
+}
diff --git a/test/qstick.c b/test/qstick.c
new file mode 100644
index 0000000..daa13d2
--- /dev/null
+++ b/test/qstick.c
@@ -0,0 +1,24 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_qstick( void )
+{
+ fp_t const x0[] = { 10, 10, 10, 20, 20, 30 };
+ fp_t const x1[] = { 12, 13, 12, 22, 23, 33 };
+ fp_t const x2[] = { 9, 9, 10, 19, 20, 29 };
+ fp_t const x3[] = { 11, 12, 11, 21, 22, 31 };
+
+ fp_t const * const x[] = { x0, x1, x2, x3 };
+
+ uint_t k = 3;
+ fp_t e[4];
+ e[0] = ((11 - 10) + (12 - 10) + (11 - 10)) / 3.0;
+ e[1] = e[0] + ((21 - 20) - (11 - 10)) / 3.0;
+ e[2] = e[1] + ((22 - 20) - (12 - 10)) / 3.0;
+ e[3] = e[2] + ((31 - 30) - (11 - 10)) / 3.0;
+
+ fp_t const * const exp[1] = { e };
+
+ return ts_test(ts_qstick, ts_qstick_offset, x, exp, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&k, &ts_qstick_p);
+}
diff --git a/test/radian_degree.c b/test/radian_degree.c
new file mode 100644
index 0000000..f3f74aa
--- /dev/null
+++ b/test/radian_degree.c
@@ -0,0 +1,14 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_radian_degree( void )
+{
+ fp_t const x[] = { 0, PI / 6, PI / 4, PI / 3, HALF_PI, PI, 3 * HALF_PI, 2 * PI };
+ fp_t const e[] = { 0, 30, 45, 60, 90, 180, 270, 360 };
+
+ fp_t const * const inp[1] = { x };
+ fp_t const * const exp[1] = { e };
+
+ return ts_test(ts_radian_degree, ts_radian_degree_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)NULL, &ts_radian_degree_p);
+}
diff --git a/test/rainbow_ma.c b/test/rainbow_ma.c
new file mode 100644
index 0000000..a5fefe3
--- /dev/null
+++ b/test/rainbow_ma.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_rainbow_ma( void )
+{
+ fp_t const x0[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const e0[] = { 15.0, 16.0 };
+
+ fp_t const * const x[] = { x0 };
+ fp_t const * const e[] = { e0 };
+
+ return ts_test(ts_rainbow_ma, ts_rainbow_ma_offset, x, e, 21, (ts_constant_t *)NULL, &ts_rainbow_ma_p);
+}
diff --git a/test/rainbow_oscillator.c b/test/rainbow_oscillator.c
new file mode 100644
index 0000000..a529ea2
--- /dev/null
+++ b/test/rainbow_oscillator.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_rainbow_oscillator( void )
+{
+ fp_t const x0[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+
+ fp_t const e0[] = { 108.333333, 108.333333, 108.333333, 108.333333, 108.333333, 108.333333, 108.333333, 108.333333 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_rainbow_oscillator, ts_rainbow_oscillator_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_rainbow_oscillator_p);
+}
diff --git a/test/random_walk_index.c b/test/random_walk_index.c
new file mode 100644
index 0000000..6702fca
--- /dev/null
+++ b/test/random_walk_index.c
@@ -0,0 +1,19 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_random_walk_index( void )
+{
+ fp_t const x0[] = { 10, 12, 11, 13, 12, 14, 13 };
+ fp_t const * const x[] = { x0 };
+
+ uint_t k = 3;
+
+ fp_t sum_abs = 2.0 + 1.0 + 2.0;
+ fp_t sum_net = ( 12 - 10 ) + ( 11 - 12 ) + ( 13 - 11 );
+ fp_t const e0[] = { sum_abs / sum_net, 0, sum_abs / sum_net, 0 };
+
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_random_walk_index, ts_random_walk_index_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&k, &ts_random_walk_index_p);
+}
diff --git a/test/rank.c b/test/rank.c
new file mode 100644
index 0000000..2c96247
--- /dev/null
+++ b/test/rank.c
@@ -0,0 +1,20 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_rank( void )
+{
+ /* k=3, data: 10, 20, 15, 25, 5, 30 */
+ /* i=2 (window [10,20,15]): current=15, <=: 10,15 => 2/3*100 = 66.667 */
+ /* i=3 (window [20,15,25]): current=25, <=: 20,15,25 => 3/3*100 = 100.0 */
+ /* i=4 (window [15,25,5]): current=5, <=: 5 => 1/3*100 = 33.333 */
+ /* i=5 (window [25,5,30]): current=30, <=: 25,5,30 => 3/3*100 = 100.0 */
+ fp_t const x[] = { 10, 20, 15, 25, 5, 30 };
+
+ fp_t const * const inp[1] = { x };
+ fp_t const e[] = { 66.66667, 100.0, 33.33333, 100.0 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_rank, ts_rank_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_rank_p);
+}
diff --git a/test/ratio.c b/test/ratio.c
new file mode 100644
index 0000000..beb0f43
--- /dev/null
+++ b/test/ratio.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_ratio( void )
+{
+ uint_t const c[] = { 3 };
+
+ fp_t const x0[] = { 1, 2, 3, 4, 5, 6 };
+ fp_t const e0[] = { 4, 2.5, 2 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_ratio, ts_ratio_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_ratio_p);
+}
diff --git a/test/ravi.c b/test/ravi.c
new file mode 100644
index 0000000..22fb09c
--- /dev/null
+++ b/test/ravi.c
@@ -0,0 +1,66 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_ravi( void )
+{
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 };
+ fp_t const cl[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+
+ fp_t const * const inp[4] = { o, h, l, cl };
+
+ /* k=3, k2=3 */
+ /* Numerator per bar: (close-open) + (close-prev_close) */
+ /* i=0: 1+0=1, i=1: 1+1=2, i=2: 1+1=2, i=3: 1+1=2, ... */
+ /* Denominator per bar: abs(high-low) + abs(close-open) */
+ /* i=0: 4+1=5, i=1: 4+1=5, ... */
+ /* SMA3(num): (1+2+2)/3=5/3, (2+2+2)/3=2, (2+2+2)/3=2, ... */
+ /* SMA3(den): 5, 5, 5, ... */
+ /* RVI: 5/15=1/3, 2/5, 2/5, 2/5, ... */
+
+ fp_t e_rvi[6] = { 0 };
+ fp_t e_sig[6] = { 0 };
+
+ fp_t num_val[] = { 1, 2, 2, 2, 2, 2, 2, 2, 2, 2 };
+ fp_t den_val[] = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 };
+
+ fp_t num_sum = num_val[0] + num_val[1] + num_val[2];
+ fp_t den_sum = den_val[0] + den_val[1] + den_val[2];
+
+ fp_t rvi_vals[10] = { 0 };
+ for( uint_t i = 2; i < 10; ++i )
+ {
+ if( i >= 3 )
+ {
+ num_sum = num_sum - num_val[i - 3] + num_val[i];
+ den_sum = den_sum - den_val[i - 3] + den_val[i];
+ }
+ rvi_vals[i] = ( den_sum != 0 ) ? ( num_sum / 3.0 ) / ( den_sum / 3.0 ) : 0;
+ }
+
+ uint_t offset = 4;
+ for( uint_t i = offset; i < 10; ++i )
+ {
+ e_rvi[i - offset] = rvi_vals[i];
+ }
+
+ fp_t sig_a = 1.0 / 3;
+ fp_t sig_e = rvi_vals[2];
+ for( uint_t i = 2; i < 10; ++i )
+ {
+ sig_e = sig_e + sig_a * ( rvi_vals[i] - sig_e );
+ if( i >= offset )
+ {
+ e_sig[i - offset] = sig_e;
+ }
+ }
+
+ fp_t const * const exp[2] = { e_rvi, e_sig };
+
+ typedef struct { uint_t k; fp_t k2; } c_t;
+ c_t cv = { .k = 3, .k2 = 3 };
+
+ return ts_test(ts_ravi, ts_ravi_offset, inp, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&cv, &ts_ravi_p);
+}
diff --git a/test/reciprocal.c b/test/reciprocal.c
new file mode 100644
index 0000000..9396277
--- /dev/null
+++ b/test/reciprocal.c
@@ -0,0 +1,10 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+bool ts_test_reciprocal( void ) {
+ fp_t const x0[] = { 1, 2, 3, 4, 5, 0.5, -1, 0 };
+ fp_t const e0[] = { 1, 0.5, 0.333333, 0.25, 0.2, 2, -1, INFINITY };
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+ return ts_test(ts_reciprocal, ts_reciprocal_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_reciprocal_p);
+}
diff --git a/test/regression_linear_least_squares.c b/test/regression_linear_least_squares.c
new file mode 100644
index 0000000..871c73e
--- /dev/null
+++ b/test/regression_linear_least_squares.c
@@ -0,0 +1,19 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_regression_linear_least_squares( void )
+{
+ uint_t const c[] = { 3 };
+
+ fp_t const x0[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+ fp_t const x1[] = { 2, 4, 6, 8, 10, 12, 14, 16 };
+
+ fp_t const e0[] = { 2, 2, 2, 2, 2 };
+ fp_t const e1[] = { 0, 0, 0, 0, 0 };
+ fp_t const e2[] = { 1, 1, 1, 1, 1 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[3] = { e0, e1, e2 };
+
+ return ts_test(ts_regression_linear_least_squares, ts_regression_linear_least_squares_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_regression_linear_least_squares_p);
+}
diff --git a/test/relative_strength.c b/test/relative_strength.c
new file mode 100644
index 0000000..8b546ea
--- /dev/null
+++ b/test/relative_strength.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_relative_strength( void )
+{
+ fp_t const x0[] = { 10, 12, 11, 13, 12 };
+ fp_t const x1[] = { 10, 10, 10, 10, 10 };
+ fp_t const e0[] = { 100, 120, 110, 130, 120 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_relative_strength, ts_relative_strength_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_relative_strength_p);
+}
diff --git a/test/relative_volatility.c b/test/relative_volatility.c
new file mode 100644
index 0000000..730812b
--- /dev/null
+++ b/test/relative_volatility.c
@@ -0,0 +1,21 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_relative_volatility( void )
+{
+ fp_t const x0[] = { 10, 10, 10, 10, 20, 20, 20 };
+ fp_t const * const x[] = { x0 };
+
+ /* k=3, offset=2, y_n=5 */
+ /* y[2]: [10,10,10] -> 0 (overwritten by xi=3: also [10,10,10] -> 0) */
+ /* y[3]: [10,10,20] mean=13.33, sd=4.71 -> 35.355339 */
+ /* y[4]: [10,20,20] mean=16.67, sd=4.71 -> 28.284271 */
+ /* y[5]: [20,20,20] -> 0 */
+ /* y[6]: not written -> 0 */
+ fp_t const e[] = { 0.0, 35.355339, 28.284271, 0.0, 0.0 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_relative_volatility, ts_relative_volatility_offset, x, exp, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&k, &ts_relative_volatility_p);
+}
diff --git a/test/relu.c b/test/relu.c
new file mode 100644
index 0000000..fcdb229
--- /dev/null
+++ b/test/relu.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_relu( void )
+{
+ fp_t const x0[] = { -3.000000, -1.000000, 0.000000, 1.000000, 3.000000 };
+ fp_t const e0[] = { 0.000000, 0.000000, 0.000000, 1.000000, 3.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_relu, ts_relu_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_relu_p);
+}
diff --git a/test/remainder.c b/test/remainder.c
new file mode 100644
index 0000000..735c5f3
--- /dev/null
+++ b/test/remainder.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_remainder( void )
+{
+ fp_t const x0[] = { 7.000000, 10.000000, -7.000000, 7.500000 };
+ fp_t const x1[] = { 3.000000, 3.000000, 3.000000, 2.000000 };
+ fp_t const e0[] = { 1.000000, 1.000000, -1.000000, -0.500000 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_remainder, ts_remainder_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_remainder_p);
+}
diff --git a/test/remquo_dummy.c b/test/remquo_dummy.c
new file mode 100644
index 0000000..1f49d73
--- /dev/null
+++ b/test/remquo_dummy.c
@@ -0,0 +1,11 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_remquo_dummy( void )
+{
+ fp_t const x0[] = { 0, 1, 2, 3, 1.5, 2.5 };
+ fp_t const e0[] = { 0.0, 0.0, 0.0, 0.0, -0.5, 0.5 };
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+ return ts_test(ts_remquo_dummy, ts_remquo_dummy_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_remquo_dummy_p);
+}
diff --git a/test/reversal.c b/test/reversal.c
new file mode 100644
index 0000000..ccc8021
--- /dev/null
+++ b/test/reversal.c
@@ -0,0 +1,19 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_reversal( void )
+{
+ /* close: 10, 12, 11, 13, 10, 12 */
+ /* i=2: c=11, prev=12, prev2=10. 11<12 && 12>10 => -1 (top reversal) */
+ /* i=3: c=13, prev=11, prev2=12. 13>11 && 11<12 => +1 (bottom reversal) */
+ /* i=4: c=10, prev=13, prev2=11. 10<13 && 13>11 => -1 (top reversal) */
+ /* i=5: c=12, prev=10, prev2=13. 12>10 && 10<13 => +1 (bottom reversal) */
+ fp_t const x[] = { 10, 12, 11, 13, 10, 12 };
+
+ fp_t const * const inp[1] = { x };
+ fp_t const e[] = { -1, 1, -1, 1 };
+ fp_t const * const exp[1] = { e };
+
+ return ts_test(ts_reversal, ts_reversal_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)NULL, &ts_reversal_p);
+}
diff --git a/test/rint.c b/test/rint.c
new file mode 100644
index 0000000..9b1ec0d
--- /dev/null
+++ b/test/rint.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_rint( void )
+{
+ fp_t const x0[] = { -2.700000, -1.300000, 0.300000, 0.700000, 1.500000, 2.700000 };
+ fp_t const e0[] = { -3.000000, -1.000000, 0.000000, 1.000000, 2.000000, 3.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_rint, ts_rint_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_rint_p);
+}
diff --git a/test/roc.c b/test/roc.c
new file mode 100644
index 0000000..0e7ddfc
--- /dev/null
+++ b/test/roc.c
@@ -0,0 +1,19 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_roc( void )
+{
+ /* k=3, close: 10, 12, 14, 16, 18, 20 */
+ /* i=3: (16-10)/10*100 = 60 */
+ /* i=4: (18-12)/12*100 = 50 */
+ /* i=5: (20-14)/14*100 = 42.857 */
+ fp_t const x[] = { 10, 12, 14, 16, 18, 20 };
+
+ fp_t const * const inp[1] = { x };
+ fp_t const e[] = { 60, 50, 42.857143 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_roc, ts_roc_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_roc_p);
+}
diff --git a/test/rocp.c b/test/rocp.c
new file mode 100644
index 0000000..589e161
--- /dev/null
+++ b/test/rocp.c
@@ -0,0 +1,19 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_rocp( void )
+{
+ /* k=3, close: 10, 12, 14, 16, 18, 20 */
+ /* i=3: (16-10)/10 = 0.6 */
+ /* i=4: (18-12)/12 = 0.5 */
+ /* i=5: (20-14)/14 = 0.42857 */
+ fp_t const x[] = { 10, 12, 14, 16, 18, 20 };
+
+ fp_t const * const inp[1] = { x };
+ fp_t const e[] = { 0.6, 0.5, 0.42857 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_rocp, ts_rocp_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_rocp_p);
+}
diff --git a/test/rocr.c b/test/rocr.c
new file mode 100644
index 0000000..60335af
--- /dev/null
+++ b/test/rocr.c
@@ -0,0 +1,19 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_rocr( void )
+{
+ /* k=3, close: 10, 12, 14, 16, 18, 20 */
+ /* i=3: 16/10 = 1.6 */
+ /* i=4: 18/12 = 1.5 */
+ /* i=5: 20/14 = 1.42857 */
+ fp_t const x[] = { 10, 12, 14, 16, 18, 20 };
+
+ fp_t const * const inp[1] = { x };
+ fp_t const e[] = { 1.6, 1.5, 1.42857 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_rocr, ts_rocr_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_rocr_p);
+}
diff --git a/test/rocr100.c b/test/rocr100.c
new file mode 100644
index 0000000..45578ab
--- /dev/null
+++ b/test/rocr100.c
@@ -0,0 +1,19 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_rocr100( void )
+{
+ /* k=3, close: 10, 12, 14, 16, 18, 20 */
+ /* i=3: 16/10*100 = 160 */
+ /* i=4: 18/12*100 = 150 */
+ /* i=5: 20/14*100 = 142.857 */
+ fp_t const x[] = { 10, 12, 14, 16, 18, 20 };
+
+ fp_t const * const inp[1] = { x };
+ fp_t const e[] = { 160, 150, 142.857143 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_rocr100, ts_rocr100_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_rocr100_p);
+}
diff --git a/test/root.c b/test/root.c
new file mode 100644
index 0000000..6b9822b
--- /dev/null
+++ b/test/root.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_root( void )
+{
+ fp_t const c[] = { 2.0 };
+
+ fp_t const x0[] = { 0, 1, 4, 9, 16 };
+ fp_t const e0[] = { 0, 1, 2, 3, 4 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_root, ts_root_offset, x, e, sizeof( c ) / sizeof( *c ), (ts_constant_t *)&c, &ts_root_p);
+}
diff --git a/test/root_cube.c b/test/root_cube.c
new file mode 100644
index 0000000..c60f7cb
--- /dev/null
+++ b/test/root_cube.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_root_cube( void )
+{
+ fp_t const x0[] = { -8, -1, 0, 1, 8, 27, 64 };
+ fp_t const e0[] = { -2, -1, 0, 1, 2, 3, 4 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_root_cube, ts_root_cube_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_root_cube_p);
+}
diff --git a/test/root_square.c b/test/root_square.c
new file mode 100644
index 0000000..2ee33c6
--- /dev/null
+++ b/test/root_square.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_root_square( void )
+{
+ fp_t const x0[] = { 0, 1, 4, 9, 16, 25 };
+ fp_t const e0[] = { 0, 1, 2, 3, 4, 5 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_root_square, ts_root_square_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_root_square_p);
+}
diff --git a/test/round_.c b/test/round_.c
new file mode 100644
index 0000000..c9a29ad
--- /dev/null
+++ b/test/round_.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_round_( void )
+{
+ fp_t const x0[] = { -2.7, -1.3, 0, 0.5, 1.3, 2.7, 3.9 };
+ fp_t const e0[] = { -3, -1, 0, 1, 1, 3, 4 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_round_, ts_round__offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_round__p);
+}
diff --git a/test/rsi.c b/test/rsi.c
new file mode 100644
index 0000000..040a08c
--- /dev/null
+++ b/test/rsi.c
@@ -0,0 +1,30 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_rsi( void )
+{
+ /* k=3, changes: +1, +1, +1, 0, -1, -1, -1, 0, +1 */
+ fp_t const x[] = { 10, 11, 12, 13, 13, 12, 11, 10, 10, 11 };
+
+ fp_t const * const inp[1] = { x };
+
+ /* i=3: sum_gain=3, sum_loss=0, avg_gain=1, avg_loss=0 => RSI=100 */
+ /* i=4: change=0, avg_gain=(1*2+0)/3=2/3, avg_loss=0 => RSI=100 */
+ /* i=5: change=-1, avg_gain=(2/3*2+0)/3=4/9, avg_loss=(0*2+1)/3=1/3 */
+ /* RS = (4/9)/(1/3) = 4/3, RSI = 100 - 100/(1+4/3) = 100 - 300/7 = 57.142857 */
+ /* i=6: change=-1, avg_gain=(4/9*2+0)/3=8/27, avg_loss=(1/3*2+1)/3=5/9 */
+ /* RS = (8/27)/(5/9) = 8/15, RSI = 100 - 100/(1+8/15) = 100 - 1500/23 = 34.782609 */
+ /* i=7: change=-1, avg_gain=(8/27*2+0)/3=16/81, avg_loss=(5/9*2+1)/3=19/27 */
+ /* RS = (16/81)/(19/27) = 16/57, RSI = 100 - 100/(1+16/57) = 100 - 5700/73 = 21.917808 */
+ /* i=8: change=0, avg_gain=(16/81*2+0)/3=32/243, avg_loss=(19/27*2+0)/3=38/81 */
+ /* RS = (32/243)/(38/81) = 16/57, RSI = 21.917808 */
+ /* i=9: change=+1, avg_gain=(32/243*2+1)/3=307/729, avg_loss=(38/81*2+0)/3=76/243 */
+ /* RS = (307/729)/(76/243) = 307/228, RSI = 100 - 100/(1+307/228) = 100 - 22800/535 = 57.383178 */
+
+ fp_t const e[] = { 100, 100, 57.142857, 34.782609, 21.917808, 21.917808, 57.383178 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_rsi, ts_rsi_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_rsi_p);
+}
diff --git a/test/running_count.c b/test/running_count.c
new file mode 100644
index 0000000..45e35b1
--- /dev/null
+++ b/test/running_count.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_running_count( void )
+{
+ fp_t const x0[] = { 0, 1, 0, 2, 0, 3, 0, 4 };
+ fp_t const e0[] = { 0, 1, 1, 2, 2, 3, 3, 4 };
+
+ fp_t const * const x[] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_running_count, ts_running_count_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_running_count_p);
+}
diff --git a/test/schaff_trend_cycle.c b/test/schaff_trend_cycle.c
new file mode 100644
index 0000000..49c3791
--- /dev/null
+++ b/test/schaff_trend_cycle.c
@@ -0,0 +1,48 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_schaff_trend_cycle( void )
+{
+ /* period=3, offset=52, y_n=48 */
+ fp_t x[100] = {
+ 100.000000, 104.932803, 109.469637, 113.249904, 115.980586, 117.462425, 117.607714, 116.448140,
+ 114.131948, 110.910698, 107.116800, 103.133815, 99.362193, 96.183508, 93.926363, 92.837048,
+ 93.057531, 94.612780, 97.408533, 101.239717, 105.808768, 110.752209, 115.673120, 120.176596,
+ 123.905018, 126.570000, 127.978150, 128.048347, 126.818984, 124.444538, 121.181777, 117.366816,
+ 113.385098, 109.636962, 106.501880, 104.304564, 103.285957, 103.581711, 105.210072, 108.070246,
+ 111.951406, 116.551523, 121.504346, 126.412117, 130.881103, 134.556766, 137.155435, 138.489641,
+ 138.484867, 137.186202, 134.754318, 131.451109, 127.616305, 123.637121, 119.913670, 116.823220,
+ 114.686494, 113.739025, 114.110109, 115.811263, 118.735191, 122.665371, 127.295395, 132.256340,
+ 137.149724, 141.583098, 145.205106, 147.736868, 148.996894, 148.917291, 147.549835, 145.061339,
+ 141.718757, 137.865336, 133.889954, 130.192382, 127.147579, 125.072189, 124.196267, 124.642717,
+ 126.416325, 129.403321, 133.381551, 138.040314, 143.008121, 147.885875, 152.282525, 155.849995,
+ 158.314276, 159.499906, 159.345639, 157.909919, 155.365657, 151.984789, 148.113982, 144.143665,
+ 140.473160, 137.475006, 135.461681, 134.657694
+ };
+
+ fp_t const * const inp[1] = { x };
+
+ fp_t const e0[] = {
+ 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000,
+ 0.000000, 0.000000, 100.000000, 100.000000, 100.000000, 0.000000, 0.000000, 0.000000,
+ 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000,
+ 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 100.000000,
+ 100.000000, 100.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000,
+ 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000
+ };
+
+ fp_t const e1[] = {
+ 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000,
+ 0.000000, 0.000000, 33.333333, 55.555556, 70.370370, 46.913580, 31.275720, 20.850480,
+ 13.900320, 9.266880, 6.177920, 4.118613, 2.745742, 1.830495, 1.220330, 0.813553,
+ 0.542369, 0.361579, 0.241053, 0.160702, 0.107135, 0.071423, 0.047615, 33.365077,
+ 55.576718, 70.384479, 46.922986, 31.281991, 20.854660, 13.903107, 9.268738, 6.179159,
+ 4.119439, 2.746293, 1.830862, 1.220575, 0.813716, 0.542478, 0.361652, 0.241101
+ };
+
+ fp_t const * const exp[2] = { e0, e1 };
+
+ uint_t period = 3;
+ return ts_test(ts_schaff_trend_cycle, ts_schaff_trend_cycle_offset, inp, exp, 100, (ts_constant_t *)&period, &ts_schaff_trend_cycle_p);
+}
diff --git a/test/secant.c b/test/secant.c
new file mode 100644
index 0000000..aef0c4d
--- /dev/null
+++ b/test/secant.c
@@ -0,0 +1,9 @@
+#include <stddef.h>
+#include "ts.h"
+bool ts_test_secant( void ) {
+ fp_t const x0[] = { 0, 0.5, 1.0, 1.5, 2.0 };
+ fp_t const e0[] = { 1.000000, 1.139494, 1.850816, 14.136833, -2.402998 };
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+ return ts_test(ts_secant, ts_secant_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_secant_p);
+}
diff --git a/test/secant_hyperbolic.c b/test/secant_hyperbolic.c
new file mode 100644
index 0000000..d95f6b8
--- /dev/null
+++ b/test/secant_hyperbolic.c
@@ -0,0 +1,9 @@
+#include <stddef.h>
+#include "ts.h"
+bool ts_test_secant_hyperbolic( void ) {
+ fp_t const x0[] = { 0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0 };
+ fp_t const e0[] = { 1.000000, 0.886819, 0.648054, 0.425096, 0.265802, 0.163071, 0.099328, 0.060340, 0.036619 };
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+ return ts_test(ts_secant_hyperbolic, ts_secant_hyperbolic_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_secant_hyperbolic_p);
+}
diff --git a/test/secant_hyperbolic_inverse.c b/test/secant_hyperbolic_inverse.c
new file mode 100644
index 0000000..b8801a9
--- /dev/null
+++ b/test/secant_hyperbolic_inverse.c
@@ -0,0 +1,9 @@
+#include <stddef.h>
+#include "ts.h"
+bool ts_test_secant_hyperbolic_inverse( void ) {
+ fp_t const x0[] = { 0.9, 0.8, 0.7, 0.5, 0.1 };
+ fp_t const e0[] = { 0.467145, 0.693147, 0.895588, 1.316958, 2.993223 };
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+ return ts_test(ts_secant_hyperbolic_inverse, ts_secant_hyperbolic_inverse_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_secant_hyperbolic_inverse_p);
+}
diff --git a/test/secant_inverse.c b/test/secant_inverse.c
new file mode 100644
index 0000000..e8c7b78
--- /dev/null
+++ b/test/secant_inverse.c
@@ -0,0 +1,9 @@
+#include <stddef.h>
+#include "ts.h"
+bool ts_test_secant_inverse( void ) {
+ fp_t const x0[] = { 2, 3, 4, 5, 10 };
+ fp_t const e0[] = { 1.047198, 1.230959, 1.318116, 1.369438, 1.470629 };
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+ return ts_test(ts_secant_inverse, ts_secant_inverse_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_secant_inverse_p);
+}
diff --git a/test/session_volume.c b/test/session_volume.c
new file mode 100644
index 0000000..d03f2ff
--- /dev/null
+++ b/test/session_volume.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_session_volume( void )
+{
+ fp_t const v[] = { 100, 200, 300, 400, 500 };
+
+ fp_t const * const x[] = { v };
+
+ fp_t e[5] = { 100, 300, 600, 1000, 1500 };
+
+ fp_t const * const e_arr[1] = { e };
+
+ return ts_test(ts_session_volume, ts_session_volume_offset, x, e_arr, 5, (ts_constant_t *)NULL, &ts_session_volume_p);
+}
diff --git a/test/shinohara_intensity_ratio.c b/test/shinohara_intensity_ratio.c
new file mode 100644
index 0000000..1e3bd09
--- /dev/null
+++ b/test/shinohara_intensity_ratio.c
@@ -0,0 +1,34 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_shinohara_intensity_ratio( void )
+{
+ fp_t const x0[] = { 10, 10, 10, 20, 20, 20 };
+ fp_t const x1[] = { 14, 13, 14, 24, 23, 24 };
+ fp_t const x2[] = { 8, 9, 8, 18, 19, 18 };
+ fp_t const x3[] = { 12, 11, 13, 22, 21, 23 };
+
+ fp_t const * const x[] = { x0, x1, x2, x3 };
+
+ uint_t k = 3;
+
+ fp_t i0 = (x1[0] - x3[0]) / (x1[0] - x2[0]);
+ fp_t i1 = (x3[1] - x2[1]) / (x1[1] - x2[1]);
+ fp_t i2 = (x1[2] - x3[2]) / (x1[2] - x2[2]);
+ fp_t e[4];
+ e[0] = (i0 + i1 + i2) / 3.0;
+
+ fp_t i3 = (x3[3] > x0[3]) ? (x1[3] - x3[3]) / (x1[3] - x2[3]) : (x3[3] - x2[3]) / (x1[3] - x2[3]);
+ e[1] = e[0] + (i3 - i0) / 3.0;
+
+ fp_t i4 = (x3[4] > x0[4]) ? (x1[4] - x3[4]) / (x1[4] - x2[4]) : (x3[4] - x2[4]) / (x1[4] - x2[4]);
+ e[2] = e[1] + (i4 - i1) / 3.0;
+
+ fp_t i5 = (x3[5] > x0[5]) ? (x1[5] - x3[5]) / (x1[5] - x2[5]) : (x3[5] - x2[5]) / (x1[5] - x2[5]);
+ e[3] = e[2] + (i5 - i2) / 3.0;
+
+ fp_t const * const exp[1] = { e };
+
+ return ts_test(ts_shinohara_intensity_ratio, ts_shinohara_intensity_ratio_offset, x, exp, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&k, &ts_shinohara_intensity_ratio_p);
+}
diff --git a/test/sigmoid.c b/test/sigmoid.c
new file mode 100644
index 0000000..5ad07e5
--- /dev/null
+++ b/test/sigmoid.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_sigmoid( void )
+{
+ fp_t const c[] = { 1, 0.5, 0.25 };
+
+ fp_t const x0[] = { 0, 0, 0, 0, 0 };
+ fp_t const e0[] = { 0.25, 0.25, 0.25, 0.25, 0.25 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_sigmoid, ts_sigmoid_offset, x, e, sizeof( c ) / sizeof( *c ), (ts_constant_t *)&c, &ts_sigmoid_p);
+}
diff --git a/test/sign.c b/test/sign.c
new file mode 100644
index 0000000..8461145
--- /dev/null
+++ b/test/sign.c
@@ -0,0 +1,9 @@
+#include <stddef.h>
+#include "ts.h"
+bool ts_test_sign( void ) {
+ fp_t const x0[] = { -4, -3, -2, -1, 0, 1, 2, 3, 4 };
+ fp_t const e0[] = { -1, -1, -1, -1, 0, 1, 1, 1, 1 };
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+ return ts_test(ts_sign, ts_sign_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_sign_p);
+}
diff --git a/test/sinc.c b/test/sinc.c
new file mode 100644
index 0000000..aa7c9fc
--- /dev/null
+++ b/test/sinc.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_sinc( void )
+{
+ fp_t const x0[] = { 0.000000, 0.500000, 1.000000, 1.500000, 2.000000, 2.500000 };
+ fp_t const e0[] = { 1.000000, 0.636620, 0.000000, -0.212207, -0.000000, 0.127324 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_sinc, ts_sinc_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_sinc_p);
+}
diff --git a/test/sinc_unnormalized.c b/test/sinc_unnormalized.c
new file mode 100644
index 0000000..e299f00
--- /dev/null
+++ b/test/sinc_unnormalized.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_sinc_unnormalized( void )
+{
+ fp_t const x0[] = { 0.000000, 0.500000, 1.000000, 1.570796, 3.141593 };
+ fp_t const e0[] = { 1.000000, 0.958851, 0.841471, 0.636620, 0.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_sinc_unnormalized, ts_sinc_unnormalized_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_sinc_unnormalized_p);
+}
diff --git a/test/sine.c b/test/sine.c
new file mode 100644
index 0000000..87cd03e
--- /dev/null
+++ b/test/sine.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_sine( void )
+{
+ fp_t const x0[] = { 0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0 };
+ fp_t const e0[] = { 0, 0.479426, 0.841471, 0.997495, 0.909297, 0.598472, 0.141120, -0.350783, -0.756802 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_sine, ts_sine_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_sine_p);
+}
diff --git a/test/sine_hyperbolic.c b/test/sine_hyperbolic.c
new file mode 100644
index 0000000..a47425e
--- /dev/null
+++ b/test/sine_hyperbolic.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_sine_hyperbolic( void )
+{
+ fp_t const x0[] = { 0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0 };
+ fp_t const e0[] = { 0.000000, 0.521095, 1.175201, 2.129279, 3.626860, 6.050204, 10.017875, 16.542627, 27.289917 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_sine_hyperbolic, ts_sine_hyperbolic_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_sine_hyperbolic_p);
+}
diff --git a/test/sine_hyperbolic_inverse.c b/test/sine_hyperbolic_inverse.c
new file mode 100644
index 0000000..01349e2
--- /dev/null
+++ b/test/sine_hyperbolic_inverse.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_sine_hyperbolic_inverse( void )
+{
+ fp_t const x0[] = { 0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0 };
+ fp_t const e0[] = { 0.000000, 0.481212, 0.881374, 1.194763, 1.443635, 1.647231, 1.818446, 1.965720, 2.094713 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_sine_hyperbolic_inverse, ts_sine_hyperbolic_inverse_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_sine_hyperbolic_inverse_p);
+}
diff --git a/test/sine_inverse.c b/test/sine_inverse.c
new file mode 100644
index 0000000..76c64d7
--- /dev/null
+++ b/test/sine_inverse.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_sine_inverse( void )
+{
+ fp_t const x0[] = { 0, 0.25, 0.5, 0.75, 1.0 };
+ fp_t const e0[] = { 0, 0.252680, 0.523599, 0.848062, 1.570796 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_sine_inverse, ts_sine_inverse_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_sine_inverse_p);
+}
diff --git a/test/slope.c b/test/slope.c
new file mode 100644
index 0000000..5fa9172
--- /dev/null
+++ b/test/slope.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_slope( void )
+{
+ uint_t const c[] = { 3 };
+
+ fp_t const x0[] = { 1, 2, 3, 4, 5, 6 };
+ fp_t const e0[] = { 1, 1, 1 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_slope, ts_slope_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_slope_p);
+}
diff --git a/test/smootherstep.c b/test/smootherstep.c
new file mode 100644
index 0000000..627c042
--- /dev/null
+++ b/test/smootherstep.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_smootherstep( void )
+{
+ fp_t const x0[] = { -1.000000, 0.000000, 0.250000, 0.500000, 0.750000, 1.000000, 2.000000 };
+ fp_t const e0[] = { 0.000000, 0.000000, 0.103516, 0.500000, 0.896484, 1.000000, 1.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_smootherstep, ts_smootherstep_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_smootherstep_p);
+}
diff --git a/test/smoothstep.c b/test/smoothstep.c
new file mode 100644
index 0000000..c7d87ff
--- /dev/null
+++ b/test/smoothstep.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_smoothstep( void )
+{
+ fp_t const x0[] = { -1.000000, 0.000000, 0.250000, 0.500000, 0.750000, 1.000000, 2.000000 };
+ fp_t const e0[] = { 0.000000, 0.000000, 0.156250, 0.500000, 0.843750, 1.000000, 1.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_smoothstep, ts_smoothstep_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_smoothstep_p);
+}
diff --git a/test/softplus.c b/test/softplus.c
new file mode 100644
index 0000000..967e1a5
--- /dev/null
+++ b/test/softplus.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_softplus( void )
+{
+ fp_t const x0[] = { -2.000000, -1.000000, 0.000000, 1.000000, 2.000000, 5.000000 };
+ fp_t const e0[] = { 0.126928, 0.313262, 0.693147, 1.313262, 2.126928, 5.006715 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_softplus, ts_softplus_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_softplus_p);
+}
diff --git a/test/softsign.c b/test/softsign.c
new file mode 100644
index 0000000..16cf82b
--- /dev/null
+++ b/test/softsign.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_softsign( void )
+{
+ fp_t const x0[] = { -3.000000, -1.000000, 0.000000, 1.000000, 3.000000 };
+ fp_t const e0[] = { -0.750000, -0.500000, 0.000000, 0.500000, 0.750000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_softsign, ts_softsign_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_softsign_p);
+}
diff --git a/test/standard_error.c b/test/standard_error.c
new file mode 100644
index 0000000..de6b488
--- /dev/null
+++ b/test/standard_error.c
@@ -0,0 +1,15 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_standard_error( void )
+{
+ fp_t const x[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+ fp_t const * const inp[1] = { x };
+
+ fp_t const e[] = { 0.0, 0.0, 0.0, 0.0 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 5;
+ return ts_test(ts_standard_error, ts_standard_error_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_standard_error_p);
+}
diff --git a/test/starc_bands.c b/test/starc_bands.c
new file mode 100644
index 0000000..0e60e5b
--- /dev/null
+++ b/test/starc_bands.c
@@ -0,0 +1,25 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_starc_bands( void )
+{
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
+ fp_t const cp[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+
+ fp_t const * const x[] = { o, h, l, cp };
+
+ /* k=3, mult=2.0 */
+ /* ATR=3.0, Middle: 13,14,15,16,17,18,19 */
+ /* Upper=Middle+2*ATR, Lower=Middle-2*ATR */
+ fp_t const eu[] = { 19, 20, 21, 22, 23, 24, 25 };
+ fp_t const em[] = { 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const el[] = { 7, 8, 9, 10, 11, 12, 13 };
+ fp_t const * const exp[3] = { eu, em, el };
+
+ typedef struct { uint_t k; fp_t mult; } c_t;
+ c_t c = { .k = 3, .mult = 2.0 };
+ return ts_test(ts_starc_bands, ts_starc_bands_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&c, &ts_starc_bands_p);
+}
diff --git a/test/starc_lower.c b/test/starc_lower.c
new file mode 100644
index 0000000..1467231
--- /dev/null
+++ b/test/starc_lower.c
@@ -0,0 +1,22 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_starc_lower( void )
+{
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
+ fp_t const cp[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+
+ fp_t const * const x[] = { o, h, l, cp };
+
+ /* k=3, mult=2.0, ATR=3.0, Middle: 13,14,15,16,17,18,19 */
+ /* Lower = Middle - 2*ATR */
+ fp_t const e[] = { 7, 8, 9, 10, 11, 12, 13, 13, 13 };
+ fp_t const * const exp[1] = { e };
+
+ typedef struct { uint_t k; fp_t mult; } c_t;
+ c_t my_c = { .k = 3, .mult = 2.0 };
+ return ts_test(ts_starc_lower, ts_starc_lower_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&my_c, &ts_starc_lower_p);
+}
diff --git a/test/starc_upper.c b/test/starc_upper.c
new file mode 100644
index 0000000..00e612d
--- /dev/null
+++ b/test/starc_upper.c
@@ -0,0 +1,22 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_starc_upper( void )
+{
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
+ fp_t const cp[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+
+ fp_t const * const x[] = { o, h, l, cp };
+
+ /* k=3, mult=2.0, ATR=3.0, Middle: 13,14,15,16,17,18,19 */
+ /* Upper = Middle + 2*ATR */
+ fp_t const e[] = { 19, 20, 21, 22, 23, 24, 25 };
+ fp_t const * const exp[1] = { e };
+
+ typedef struct { uint_t k; fp_t mult; } c_t;
+ c_t my_c = { .k = 3, .mult = 2.0 };
+ return ts_test(ts_starc_upper, ts_starc_upper_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&my_c, &ts_starc_upper_p);
+}
diff --git a/test/std_error.c b/test/std_error.c
new file mode 100644
index 0000000..1bc050e
--- /dev/null
+++ b/test/std_error.c
@@ -0,0 +1,17 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_std_error( void )
+{
+ fp_t const x[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const * const inp[1] = { x };
+
+ /* k=3, SD=sqrt(2/3)=0.81650 for all windows */
+ /* StdError = SD/sqrt(3) = 0.81650/1.73205 = 0.47140 */
+ fp_t const e[] = { 0.47140, 0.47140, 0.47140, 0.47140, 0.47140, 0.47140, 0.47140, 0.47140 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_std_error, ts_std_error_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_std_error_p);
+}
diff --git a/test/stoch_rsi.c b/test/stoch_rsi.c
new file mode 100644
index 0000000..e2b9b54
--- /dev/null
+++ b/test/stoch_rsi.c
@@ -0,0 +1,22 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_stoch_rsi( void )
+{
+ /* rsi_period=3, stoch_period=3, offset=6 */
+ /* close: 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 */
+ /* RSI all 100 (all gains), so stoch RSI is 0/0 = 0 for all */
+ fp_t const x[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+
+ fp_t const * const inp[1] = { x };
+
+ fp_t const ek[] = { 0, 0, 0, 0, 0 };
+ fp_t const ed[] = { 0, 0, 0, 0, 0 };
+
+ fp_t const * const exp[2] = { ek, ed };
+
+ typedef struct { uint_t rsi_period; uint_t stoch_period; } c_t;
+ c_t c = { .rsi_period = 3, .stoch_period = 3 };
+ return ts_test(ts_stoch_rsi, ts_stoch_rsi_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&c, &ts_stoch_rsi_p);
+}
diff --git a/test/stochastic_fast.c b/test/stochastic_fast.c
new file mode 100644
index 0000000..a30380e
--- /dev/null
+++ b/test/stochastic_fast.c
@@ -0,0 +1,23 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_stochastic_fast( void )
+{
+ /* k=3, offset=k+2=5, y_n=5. %K=80 for all bars (range=5, C-LL=4), %D=SMA(3) of %K=80 */
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
+ fp_t const c[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ /* %K = 80 for all (HH-LL=5, C-LL=4 => 100*4/5=80), %D = SMA(3) of %K = 80 */
+ fp_t const ek[] = { 80, 80, 80, 80, 80 };
+ fp_t const ed[] = { 80, 80, 80, 80, 80 };
+
+ fp_t const * const exp[2] = { ek, ed };
+
+ uint_t k = 3;
+ return ts_test(ts_stochastic_fast, ts_stochastic_fast_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_stochastic_fast_p);
+}
diff --git a/test/stochastic_momentum_index.c b/test/stochastic_momentum_index.c
new file mode 100644
index 0000000..47697cc
--- /dev/null
+++ b/test/stochastic_momentum_index.c
@@ -0,0 +1,25 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_stochastic_momentum_index( void )
+{
+ /* k1=3, k2=3, signal_period=3, offset = k1+k2-1 = 5 */
+ /* close = (H+L)/2, so deviation = 0 always, SMI = 0 */
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
+ fp_t const c[] = { 10.5, 11.5, 12.5, 13.5, 14.5, 15.5, 16.5, 17.5, 18.5, 19.5 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ /* deviation = 0, SMI = 0, signal = 0 */
+ fp_t const esmi[] = { 0, 0, 0, 0 };
+ fp_t const esig[] = { 0, 0, 0, 0 };
+
+ fp_t const * const exp[2] = { esmi, esig };
+
+ typedef struct { uint_t k1; uint_t k2; uint_t signal_period; } c_t;
+ c_t consts = { .k1 = 3, .k2 = 3, .signal_period = 3 };
+ return ts_test(ts_stochastic_momentum_index, ts_stochastic_momentum_index_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&consts, &ts_stochastic_momentum_index_p);
+}
diff --git a/test/stochastic_slow.c b/test/stochastic_slow.c
new file mode 100644
index 0000000..1bab8c4
--- /dev/null
+++ b/test/stochastic_slow.c
@@ -0,0 +1,24 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_stochastic_slow( void )
+{
+ /* k=3, offset=3*k-3=6. Monotonic increase => all %K=80 (HH-LL=3, C-LL=2) => all smoothed = 80 */
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 };
+ fp_t const c[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ /* offset=6, so y_n = 15-6 = 9 values per output */
+ /* slow %K and %D all = 80 after initial fill */
+ fp_t const ek[] = { 80, 80, 80, 80, 80, 80, 80, 80, 80 };
+ fp_t const ed[] = { 80, 80, 80, 80, 80, 80, 80, 80, 80 };
+
+ fp_t const * const exp[2] = { ek, ed };
+
+ uint_t k = 3;
+ return ts_test(ts_stochastic_slow, ts_stochastic_slow_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_stochastic_slow_p);
+}
diff --git a/test/student_t_pdf.c b/test/student_t_pdf.c
new file mode 100644
index 0000000..f23f955
--- /dev/null
+++ b/test/student_t_pdf.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_student_t_pdf( void )
+{
+ fp_t const c[] = { 10.000000 };
+
+ fp_t const x0[] = { -2.000000, -1.000000, 0.000000, 1.000000, 2.000000 };
+ fp_t const e0[] = { 0.061146, 0.230362, 0.389108, 0.230362, 0.061146 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_student_t_pdf, ts_student_t_pdf_offset, x, e, sizeof( c ) / sizeof( *c ), (ts_constant_t *)&c, &ts_student_t_pdf_p);
+}
diff --git a/test/subtraction.c b/test/subtraction.c
new file mode 100644
index 0000000..9a62e5e
--- /dev/null
+++ b/test/subtraction.c
@@ -0,0 +1,14 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_subtraction( void )
+{
+ fp_t const x0[] = { 10, 20, 30 };
+ fp_t const x1[] = { 3, 7, 5 };
+ fp_t const e0[] = { 7, 13, 25 };
+
+ fp_t const * const x[2] = { x0, x1 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_subtraction, ts_subtraction_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_subtraction_p);
+}
diff --git a/test/summation.c b/test/summation.c
new file mode 100644
index 0000000..7ed1677
--- /dev/null
+++ b/test/summation.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_summation( void )
+{
+ uint_t const c = 3;
+
+ fp_t const x0[] = { 1, 2, 3, 4, 5 };
+ fp_t const e0[] = { 6, 9, 12 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_summation, ts_summation_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_summation_p);
+}
diff --git a/test/supertrend.c b/test/supertrend.c
new file mode 100644
index 0000000..cce08ae
--- /dev/null
+++ b/test/supertrend.c
@@ -0,0 +1,31 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_supertrend( void )
+{
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 };
+ fp_t const c[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ /* k=3, mult=2 */
+ /* TR at i=1: max(3,|13-11|=2,|10-11|=1)=3 */
+ /* TR at i=2: max(3,|14-12|=2,|11-12|=1)=3 */
+ /* TR at i=3: max(3,|15-13|=2,|12-13|=1)=3 */
+ /* ATR = (3+3+3)/3 = 3 */
+ /* Median at i=3: (15+12)/2 = 13.5 */
+ /* Upper = 13.5 + 2*3 = 19.5, Lower = 13.5 - 2*3 = 7.5 */
+ /* C[3]=14 > lower=7.5 => uptrend, supertrend=7.5 */
+ /* Next: ATR remains 3, Median=15, Upper=21, Lower=9 */
+ /* C[4]=15 > prev_st=7.5 and > lower=9 => supertrend=9 */
+ fp_t const est[] = { 7.5, 8.5, 9.5, 10.5, 11.5, 12.5, 13.5, 14.5, 15.5, 16.5, 17.5, 18.5 };
+ fp_t const etr[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
+ fp_t const * const exp[2] = { est, etr };
+
+ typedef struct { uint_t k; fp_t mult; } c_t;
+ c_t consts = { .k = 3, .mult = 2.0 };
+ return ts_test(ts_supertrend, ts_supertrend_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&consts, &ts_supertrend_p);
+}
diff --git a/test/swing_index.c b/test/swing_index.c
new file mode 100644
index 0000000..2853198
--- /dev/null
+++ b/test/swing_index.c
@@ -0,0 +1,35 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_swing_index( void )
+{
+ fp_t const o[] = { 10, 11, 12, 11, 12 };
+ fp_t const h[] = { 12, 13, 14, 12, 14 };
+ fp_t const l[] = { 8, 9, 10, 9, 10 };
+ fp_t const c[] = { 11, 12, 10, 11, 11 };
+ fp_t const v[] = { 100, 200, 300, 400, 500 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t e[4] = { 0 };
+ for( uint_t i = 1; i < 5; ++i )
+ {
+ fp_t a1 = h[i] - h[i - 1];
+ fp_t a2 = l[i - 1] - l[i];
+ fp_t a = fmax( a1, a2 );
+ fp_t b = c[i] - c[i - 1];
+ fp_t ldpl = l[i] - l[i - 1];
+ fp_t phmh = h[i - 1] - h[i];
+ fp_t c_val = 0;
+ if( b < 0 && ldpl > phmh ) c_val = ldpl;
+ else if( b >= 0 && phmh > ldpl ) c_val = phmh;
+ fp_t hl = h[i] - l[i];
+ fp_t swing = ( hl != 0 ) ? ( a * 2.0 + b - c_val ) * v[i] / hl : 0;
+ e[i - 1] = ( i > 1 ? e[i - 2] : 0 ) + swing;
+ }
+
+ fp_t const * const e_arr[1] = { e };
+
+ return ts_test(ts_swing_index, ts_swing_index_offset, x, e_arr, 5, (ts_constant_t *)NULL, &ts_swing_index_p);
+}
diff --git a/test/swish.c b/test/swish.c
new file mode 100644
index 0000000..c98e256
--- /dev/null
+++ b/test/swish.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_swish( void )
+{
+ fp_t const x0[] = { -2.000000, -1.000000, 0.000000, 1.000000, 2.000000 };
+ fp_t const e0[] = { -0.238406, -0.268941, 0.000000, 0.731059, 1.761594 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_swish, ts_swish_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_swish_p);
+}
diff --git a/test/synthetic_yield.c b/test/synthetic_yield.c
new file mode 100644
index 0000000..526b00f
--- /dev/null
+++ b/test/synthetic_yield.c
@@ -0,0 +1,24 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_synthetic_yield( void )
+{
+ /* k=3, prices: 100, 101, 102, 104, 106, 108, 110, 112, 114, 116 */
+ fp_t const x[] = { 100, 101, 102, 104, 106, 108, 110, 112, 114, 116 };
+
+ fp_t const * const inp[1] = { x };
+
+ /* i=3: (104-100)/100 * 252/3 * 100 = 336 */
+ /* i=4: (106-101)/101 * 84 * 100 = 42000/101 = 415.84158... */
+ /* i=5: (108-102)/102 * 84 * 100 = 50400/102 = 494.11765... */
+ /* i=6: (110-104)/104 * 84 * 100 = 50400/104 = 484.61538... */
+ /* i=7: (112-106)/106 * 84 * 100 = 50400/106 = 475.47170... */
+ /* i=8: (114-108)/108 * 84 * 100 = 50400/108 = 466.66667... */
+ /* i=9: (116-110)/110 * 84 * 100 = 50400/110 = 458.18182... */
+ fp_t const e[] = { 336, 415.84158, 494.11765, 484.61538, 475.47170, 466.66667, 458.18182 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_synthetic_yield, ts_synthetic_yield_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_synthetic_yield_p);
+}
diff --git a/test/t3.c b/test/t3.c
new file mode 100644
index 0000000..7ef7e8e
--- /dev/null
+++ b/test/t3.c
@@ -0,0 +1,29 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_t3( void )
+{
+ fp_t const x0[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+ uint_t n = sizeof( x0 ) / sizeof( *x0 );
+
+ // T3 uses hardcoded k=5, b=0.7
+ fp_t alpha = 2.0 / 6.0;
+ fp_t c3 = 0.7, c3m1 = -0.3, c3m2 = -1.3, c3m3 = -2.3;
+ fp_t w3 = c3, w2 = -3.0 * c3m1, w1 = 3.0 * c3m1 * c3m2, w0 = -c3m1 * c3m2 * c3m3;
+
+ fp_t e0[10];
+ fp_t ema1 = x0[0], ema2 = x0[0], ema3 = x0[0];
+ e0[0] = x0[0];
+ for ( uint_t i = 1; i < n; ++i )
+ {
+ ema1 = ema1 + alpha * ( x0[i] - ema1 );
+ ema2 = ema2 + alpha * ( ema1 - ema2 );
+ ema3 = ema3 + alpha * ( ema2 - ema3 );
+ e0[i] = w3 * ema3 + w2 * ema2 + w1 * ema1 + w0 * x0[i];
+ }
+
+ fp_t const * const x[] = { x0 };
+ fp_t const * const e[] = { e0 };
+
+ return ts_test(ts_t3, ts_t3_offset, x, e, n, (ts_constant_t *)NULL, &ts_t3_p);
+}
diff --git a/test/tangent.c b/test/tangent.c
new file mode 100644
index 0000000..a6195d4
--- /dev/null
+++ b/test/tangent.c
@@ -0,0 +1,14 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_tangent( void )
+{
+ fp_t const x0[] = { 0, 0.785398, -0.785398, 0.5, -0.5 };
+ fp_t const e0[] = { 0, 1, -1, 0.546302, -0.546302 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_tangent, ts_tangent_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_tangent_p);
+}
diff --git a/test/tangent_hyperbolic.c b/test/tangent_hyperbolic.c
new file mode 100644
index 0000000..6c41a9c
--- /dev/null
+++ b/test/tangent_hyperbolic.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_tangent_hyperbolic( void )
+{
+ fp_t const x0[] = { 0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0 };
+ fp_t const e0[] = { 0.000000, 0.462117, 0.761594, 0.905148, 0.964028, 0.986614, 0.995055, 0.998178, 0.999329 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_tangent_hyperbolic, ts_tangent_hyperbolic_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_tangent_hyperbolic_p);
+}
diff --git a/test/tangent_hyperbolic_inverse.c b/test/tangent_hyperbolic_inverse.c
new file mode 100644
index 0000000..8c7fd35
--- /dev/null
+++ b/test/tangent_hyperbolic_inverse.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_tangent_hyperbolic_inverse( void )
+{
+ fp_t const x0[] = { 0, 0.25, 0.5, 0.75 };
+ fp_t const e0[] = { 0, 0.255413, 0.549306, 0.972955 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_tangent_hyperbolic_inverse, ts_tangent_hyperbolic_inverse_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_tangent_hyperbolic_inverse_p);
+}
diff --git a/test/tangent_inverse.c b/test/tangent_inverse.c
new file mode 100644
index 0000000..33e3593
--- /dev/null
+++ b/test/tangent_inverse.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_tangent_inverse( void )
+{
+ fp_t const x0[] = { 0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0 };
+ fp_t const e0[] = { 0, 0.463648, 0.785398, 0.982794, 1.107149, 1.190290, 1.249046, 1.292497, 1.325818 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_tangent_inverse, ts_tangent_inverse_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_tangent_inverse_p);
+}
diff --git a/test/tanhshrink.c b/test/tanhshrink.c
new file mode 100644
index 0000000..ac01a7e
--- /dev/null
+++ b/test/tanhshrink.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_tanhshrink( void )
+{
+ fp_t const x0[] = { -2.000000, -1.000000, 0.000000, 1.000000, 2.000000 };
+ fp_t const e0[] = { -1.035972, -0.238406, 0.000000, 0.238406, 1.035972 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_tanhshrink, ts_tanhshrink_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_tanhshrink_p);
+}
diff --git a/test/test_python.py b/test/test_python.py
new file mode 100644
index 0000000..a4aa7e2
--- /dev/null
+++ b/test/test_python.py
@@ -0,0 +1,782 @@
+#!/usr/bin/env python3
+"""Validate Python bindings against C test files.
+
+Parses C test files to extract inputs and expected outputs, then
+calls Python bindings and compares. For test files that can't be
+parsed, falls back to calling C via subprocess.
+"""
+
+import ctypes
+import importlib.util
+import inspect
+import math
+import os
+import re
+import sys
+
+import numpy as np
+
+_ts_spec = importlib.util.spec_from_file_location(
+ "ts",
+ os.path.join(os.path.dirname(__file__), "..", "python", "ts", "__init__.py"),
+)
+if _ts_spec and _ts_spec.loader:
+ ts = importlib.util.module_from_spec(_ts_spec)
+ _ts_spec.loader.exec_module(ts)
+else:
+ print("Error: could not load ts module", file=sys.stderr)
+ sys.exit(1)
+
+_parser_spec = importlib.util.spec_from_file_location(
+ "ts_parser",
+ os.path.join(os.path.dirname(__file__), "..", "scripts", "ts_parser.py"),
+)
+if _parser_spec and _parser_spec.loader:
+ _ts_parser_mod = importlib.util.module_from_spec(_parser_spec)
+ _parser_spec.loader.exec_module(_ts_parser_mod)
+ parse_names_only = _ts_parser_mod.parse_names_only
+else:
+ print("Error: could not load ts_parser", file=sys.stderr)
+ sys.exit(1)
+
+
+def get_default_params(fname, x_n):
+ """Extract default params from a Python binding's signature.
+
+ Returns a dict of param name -> value for non-array parameters,
+ or None if the function has no extra parameters.
+ """
+ func = getattr(ts, fname, None)
+ if func is None:
+ return None
+ sig = inspect.signature(func)
+ params = {}
+ for pname, param in sig.parameters.items():
+ if param.annotation is np.ndarray:
+ continue
+ if param.default is not inspect.Parameter.empty:
+ params[pname] = param.default
+ return params if params else None
+
+
+LIB_PATH = os.path.join(os.path.dirname(__file__), "..", "build", "libts.so")
+N = 100
+SEED = 42
+
+
+def load_func_meta():
+ return parse_names_only()
+
+
+def _safe_float(text):
+ """Safely convert a C expression to float without eval()."""
+ text = text.strip()
+ if not text:
+ return None
+ text = re.sub(r"\bM_PI\b", "3.14159265358979323846", text)
+ text = re.sub(r"\bM_E\b", "2.71828182845904523536", text)
+ text = re.sub(r"\bPI\b", "3.14159265358979323846", text)
+ # Only allow simple numeric expressions: numbers, operators, parens, abs/min/max
+ if not re.match(r"^[\d\s\+\-\*\/\(\)\.,eE]+$", text):
+ return None
+ try:
+ import ast
+
+ tree = ast.parse(text, mode="eval")
+ # Only allow safe node types
+ for node in ast.walk(tree):
+ if not isinstance(
+ node,
+ (
+ ast.Expression,
+ ast.Constant,
+ ast.BinOp,
+ ast.UnaryOp,
+ ast.USub,
+ ast.UAdd,
+ ast.Add,
+ ast.Sub,
+ ast.Mult,
+ ast.Div,
+ ),
+ ):
+ return None
+ return float(
+ eval(text, {"__builtins__": {}, "abs": abs, "min": min, "max": max})
+ )
+ except Exception:
+ return None
+
+
+def parse_array_initializer(text):
+ """Parse a C array initializer like { 1, 2, 3.5, -4.0 } into a list of floats."""
+ m = re.search(r"\{\s*(.*?)\}\s*;", text, re.DOTALL)
+ if not m:
+ return None
+ values = []
+ for v in m.group(1).split(","):
+ v = v.strip().split("//")[0].strip()
+ if not v:
+ continue
+ result = _safe_float(v)
+ if result is None:
+ return None
+ values.append(result)
+ return values if values else None
+
+
+def parse_malloc_loops(content):
+ """Parse malloc + loop patterns to compute inputs.
+
+ Handles patterns like:
+ uint_t n = 14;
+ fp_t *x[4];
+ x[0] = malloc( n * sizeof( fp_t ) );
+ ...
+ for ( uint_t i = 0; i < n; ++i )
+ {
+ x[0][i] = 10 + i;
+ x[1][i] = 12 + i;
+ }
+
+ Returns (inputs, n) or (None, None).
+ """
+ # Find n
+ n_match = re.search(r"uint_t\s+n\s*=\s*(\d+)", content)
+ if not n_match:
+ return None, None
+ n = int(n_match.group(1))
+
+ # Find malloc declarations
+ malloc_match = re.search(r"fp_t\s+\*\s*x\[(\d+)\]", content)
+ if not malloc_match:
+ return None, None
+ x_n = int(malloc_match.group(1))
+
+ # Find loop body: x[i][j] = expr;
+ loop_match = re.search(
+ r"for\s*\([^)]*i\s*=\s*0[^)]*i\s*<\s*n[^)]*\+\+i\s*\)\s*\{([^}]+)\}",
+ content,
+ re.DOTALL,
+ )
+ if not loop_match:
+ return None, None
+
+ loop_body = loop_match.group(1)
+ formulas = {}
+ for fm in re.finditer(r"x\[(\d+)\]\[i\]\s*=\s*([^;]+);", loop_body):
+ idx = int(fm.group(1))
+ expr = fm.group(2).strip()
+ formulas[idx] = expr
+
+ if not formulas:
+ return None, None
+
+ # Compute inputs
+ inputs = {}
+ for idx, expr in formulas.items():
+ arr = []
+ for i in range(n):
+ try:
+ safe_expr = expr.replace("i", str(i)).replace("n", str(n))
+ if not re.match(r"^[\d\s\+\-\*\/\(\)\.,]+$", safe_expr.strip()):
+ return None, None
+ val = float(
+ eval(
+ safe_expr,
+ {"__builtins__": {}, "abs": abs, "min": min, "max": max},
+ )
+ )
+ arr.append(val)
+ except Exception:
+ return None, None
+ inputs[idx] = arr
+
+ # Build ordered list
+ result = []
+ for i in range(x_n):
+ if i in inputs:
+ result.append(inputs[i])
+ else:
+ return None, None
+
+ return result, n
+
+
+def parse_test_file(test_path):
+ """Parse a C test file to extract inputs, expected outputs, and constants.
+
+ Returns dict with keys: inputs (list of lists), expected (list of lists),
+ constants (list of floats), n (int), or None if parsing fails.
+ """
+ with open(test_path, encoding="utf-8") as f:
+ content = f.read()
+
+ # Try malloc + loop pattern first
+ malloc_inputs, malloc_n = parse_malloc_loops(content)
+ if malloc_inputs:
+ # Find expected arrays
+ all_arrays = {}
+ for m in re.finditer(
+ r"fp_t\s+(?:const\s+)?(\w+)\[\s*\]\s*=\s*\{([^}]*)\}\s*;",
+ content,
+ re.DOTALL,
+ ):
+ arr_name = m.group(1)
+ values = parse_array_initializer(m.group(0))
+ if values is not None:
+ all_arrays[arr_name] = values
+
+ # Find expected pointer array
+ exp_match = re.search(
+ r"fp_t\s+(?:\*\s*)?const\s*\*\s*const\s+e\w*\[\s*\d*\s*\]\s*=\s*\{([^}]*)\}",
+ content,
+ )
+ expected = []
+ if exp_match:
+ exp_names = [n.strip() for n in exp_match.group(1).split(",") if n.strip()]
+ for name in exp_names:
+ if name in all_arrays:
+ expected.append(all_arrays[name])
+
+ # Find params
+ params = None
+ param_match = re.search(r"(?:c_t|params_t)\s+c\s*=\s*\{([^}]*)\}", content)
+ if param_match:
+ params = {}
+ for fm in re.finditer(r"\.(\w+)\s*=\s*([^,}]+)", param_match.group(1)):
+ fname = fm.group(1)
+ fval_text = fm.group(2).strip()
+ result = _safe_float(fval_text)
+ if result is not None:
+ params[fname] = result
+
+ # Also look for scalar parameter: uint_t k = 3; fp_t c = 2; fp_t threshold = 0.5;
+ if params is None:
+ # Try to extract params from ts_test call's 6th argument
+ ts_test_match = re.search(
+ r"ts_test\s*\([^,]+,\s*[^,]+,\s*[^,]+,\s*[^,]+,\s*[^,]+,\s*(&?(\w+)|NULL)",
+ content,
+ )
+ if ts_test_match:
+ param_arg = ts_test_match.group(2)
+ if param_arg and param_arg != "NULL":
+ # Check if it's an array like "constants[N] = { a, b }"
+ arr_match = re.search(
+ rf"fp_t\s+{param_arg}\s*\[\s*\d+\s*\]\s*=\s*\{{([^}}]*)\}}",
+ content,
+ )
+ if arr_match:
+ # Resolve variable names to values
+ var_names = [v.strip() for v in arr_match.group(1).split(",")]
+ resolved = {}
+ for vn in var_names:
+ vn = vn.strip()
+ if not vn:
+ continue
+ # Try direct numeric value
+ result = _safe_float(vn)
+ if result is not None:
+ resolved[vn] = result
+ continue
+ # Try to find variable declaration
+ var_match = re.search(
+ rf"(?:uint_t|fp_t|int)\s+(?:const\s+)?{vn}\s*=\s*([^;]+);",
+ content,
+ )
+ if var_match:
+ result = _safe_float(var_match.group(1).strip())
+ if result is not None:
+ resolved[vn] = result
+ if resolved:
+ params = resolved
+
+ if params is None:
+ scalar_match = re.search(
+ r"(?:uint_t|fp_t|int)\s+(?:const\s+)?(\w+)\s*=\s*([^;]+);", content
+ )
+ if scalar_match:
+ var_name = scalar_match.group(1)
+ # Skip common non-param variables
+ if var_name not in ("n", "offset", "y_n", "i", "j"):
+ val_text = scalar_match.group(2).strip()
+ result = _safe_float(val_text)
+ if result is not None:
+ params = {var_name: result}
+
+ return {
+ "inputs": malloc_inputs,
+ "expected": expected,
+ "constants": [],
+ "params": params,
+ "n": malloc_n,
+ }
+
+ # Find all array declarations: fp_t const name[] = { ... };
+ all_arrays = {}
+ for m in re.finditer(
+ r"fp_t\s+(?:const\s+)?(\w+)\[\s*\]\s*=\s*\{([^}]*)\}\s*;", content, re.DOTALL
+ ):
+ arr_name = m.group(1)
+ values = parse_array_initializer(m.group(0))
+ if values is not None:
+ all_arrays[arr_name] = values
+
+ if not all_arrays:
+ return None
+
+ # Find input pointer array: fp_t const * const inp[N] = { x0, x1, ... };
+ inp_match = re.search(
+ r"fp_t\s+const\s*\*\s*const\s+(?:inp\w*|x\w*)\[\s*\d*\]\s*=\s*\{([^}]*)\}",
+ content,
+ )
+ if not inp_match:
+ return None
+ inp_names = [n.strip() for n in inp_match.group(1).split(",") if n.strip()]
+
+ # Find expected pointer array: fp_t const * const exp[N] = { ezz, ed, ... };
+ # or: fp_t const * const e[N] = { e0, e1, ... };
+ exp_match = re.search(
+ r"fp_t\s+const\s*\*\s*const\s+e\w*\[\s*\d*\s*\]\s*=\s*\{([^}]*)\}", content
+ )
+ if not exp_match:
+ return None
+ exp_names = [n.strip() for n in exp_match.group(1).split(",") if n.strip()]
+
+ # Build input arrays in order
+ inputs = []
+ for name in inp_names:
+ if name in all_arrays:
+ inputs.append(all_arrays[name])
+ else:
+ return None
+
+ # Build expected arrays in order (may be partial if some can't be resolved)
+ expected = []
+ for name in exp_names:
+ if name in all_arrays:
+ expected.append(all_arrays[name])
+ # Skip unresolved expected arrays (computed at runtime)
+
+ # Find n
+ if inputs:
+ n = len(inputs[0])
+ else:
+ return None
+
+ # Find constants
+ constants = []
+ # Look for: fp_t threshold = 5; or uint_t k = 3; or similar
+ for m in re.finditer(
+ r"(?:fp_t|uint_t|int)\s+(?:const\s+)?(\w+)\s*=\s*([^;]+);", content
+ ):
+ const_name = m.group(1)
+ const_val_text = m.group(2).strip()
+ # Skip if this is an array declaration
+ if "[" in const_val_text:
+ continue
+ result = _safe_float(const_val_text)
+ if result is not None:
+ constants.append(result)
+
+ # Find struct parameter: c_t c = { .k = 3, .std_mult = 2.0 };
+ params = None
+ param_match = re.search(r"(?:c_t|params_t)\s+c\s*=\s*\{([^}]*)\}", content)
+ if param_match:
+ params = {}
+ for fm in re.finditer(r"\.(\w+)\s*=\s*([^,}]+)", param_match.group(1)):
+ fname = fm.group(1)
+ fval_text = fm.group(2).strip()
+ result = _safe_float(fval_text)
+ if result is not None:
+ params[fname] = result
+
+ # Also look for scalar parameter: uint_t k = 3; fp_t c = 2; fp_t threshold = 0.5;
+ if params is None:
+ # Try to extract params from ts_test call's 6th argument
+ ts_test_match = re.search(
+ r"ts_test\s*\([^,]+,\s*[^,]+,\s*[^,]+,\s*[^,]+,\s*[^,]+,\s*(&?(\w+)|NULL)",
+ content,
+ )
+ if ts_test_match:
+ param_arg = ts_test_match.group(2)
+ if param_arg and param_arg != "NULL":
+ # Check if it's an array like "constants[N] = { a, b }"
+ arr_match = re.search(
+ rf"fp_t\s+{param_arg}\s*\[\s*\d+\s*\]\s*=\s*\{{([^}}]*)\}}",
+ content,
+ )
+ if arr_match:
+ var_names = [v.strip() for v in arr_match.group(1).split(",")]
+ resolved = {}
+ for vn in var_names:
+ vn = vn.strip()
+ if not vn:
+ continue
+ result = _safe_float(vn)
+ if result is not None:
+ resolved[vn] = result
+ continue
+ var_match = re.search(
+ rf"(?:uint_t|fp_t|int)\s+(?:const\s+)?{vn}\s*=\s*([^;]+);",
+ content,
+ )
+ if var_match:
+ result = _safe_float(var_match.group(1).strip())
+ if result is not None:
+ resolved[vn] = result
+ if resolved:
+ params = resolved
+
+ if params is None:
+ scalar_match = re.search(
+ r"(?:uint_t|fp_t|int)\s+(?:const\s+)?(\w+)\s*=\s*([^;]+);", content
+ )
+ if scalar_match:
+ var_name = scalar_match.group(1)
+ if var_name not in ("n", "offset", "y_n", "i", "j"):
+ val_text = scalar_match.group(2).strip()
+ result = _safe_float(val_text)
+ if result is not None:
+ params = {var_name: result}
+
+ return {
+ "inputs": inputs,
+ "expected": expected,
+ "constants": constants,
+ "params": params,
+ "n": n,
+ }
+
+
+def call_c_direct(name, x_n, y_n, inputs, n, params=None):
+ """Call C flat wrapper directly via ctypes. Returns list of lists or None.
+
+ Parameterized functions (params != None) are skipped — the C test harness
+ (make check) validates all 349 functions including those with parameters.
+ """
+ if params:
+ return None
+ try:
+ flat_func = getattr(ts._lib, f"ts_{name}_flat", None)
+ if flat_func is None:
+ return None
+ np_inputs = [np.array(arr, dtype=np.float64) for arr in inputs]
+ y_bufs = [np.empty(n, dtype=np.float64) for _ in range(y_n)]
+ args = (
+ [b.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) for b in y_bufs]
+ + [b.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) for b in np_inputs]
+ + [ctypes.c_uint64(n), None]
+ )
+ ret = flat_func(*args)
+ if ret != 0:
+ return None
+ return [b.tolist() for b in y_bufs]
+ except (OSError, AttributeError, ctypes.ArgumentError):
+ return None
+
+
+def isclose(a, b, rel_tol=1e-6, abs_tol=1e-6):
+ if math.isnan(a) and math.isnan(b):
+ return True
+ if math.isinf(a) and math.isinf(b):
+ return (a > 0) == (b > 0)
+ return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
+
+
+def arrays_close(a, b):
+ if len(a) != len(b):
+ return False
+ for x, y in zip(a, b):
+ if not isclose(x, y):
+ return False
+ return True
+
+
+def normalize_result(result, y_n, n):
+ """Normalize Python binding output to list of lists."""
+ if isinstance(result, (int, float, np.number)):
+ return [[float(result)] * n]
+ if isinstance(result, tuple):
+ return [r.tolist() if hasattr(r, "tolist") else list(r) for r in result]
+ if hasattr(result, "tolist"):
+ return [result.tolist()]
+ if isinstance(result, list) and len(result) > 0 and hasattr(result[0], "tolist"):
+ return [r.tolist() for r in result]
+ if isinstance(result, list):
+ return result
+ return [[float(result)]]
+
+
+def main():
+ status = _run_tests()
+ sys.exit(status)
+
+
+def _run_tests():
+ meta = load_func_meta()
+ test_dir = os.path.dirname(__file__)
+
+ total = len(meta)
+ passed = 0
+ failed = 0
+ skipped = 0
+
+ sorted_names = sorted(meta.keys())
+ rng = __import__("random").Random(SEED)
+ # Pre-compute RNG offsets for each function
+ rng_offsets = {}
+ offset = 0
+ for _fname in sorted_names:
+ rng_offsets[_fname] = offset
+ _x_n = meta[_fname]["x_n"]
+ offset += _x_n * N
+
+ for fname, info in sorted(meta.items()):
+ x_n = info["x_n"]
+ y_n = info["y_n"]
+
+ # Try parsing test file first
+ test_path = os.path.join(test_dir, f"{fname}.c")
+ parsed = None
+ if os.path.exists(test_path):
+ parsed = parse_test_file(test_path)
+
+ inputs = None
+ expected = None
+ params = None
+ n = N
+
+ if parsed:
+ inputs = parsed["inputs"]
+ expected = parsed["expected"]
+ params = parsed.get("params")
+ n = parsed["n"]
+
+ # Validate input count (expected count is optional - some tests compute at runtime)
+ # Note: some C functions declare wrong x_n (e.g., x_n=1 but access x[4]).
+ # If test file provides more inputs, trust the test file.
+ if len(inputs) < x_n:
+ parsed = None
+ # Truncate extra inputs to match function's declared x_n
+ if len(inputs) > x_n:
+ inputs = inputs[:x_n]
+
+ if not parsed:
+ # Fallback: random inputs + default params from binding signature
+ rng.seed(SEED)
+ rng.jumpahead(rng_offsets.get(fname, 0)) if hasattr(
+ rng, "jumpahead"
+ ) else None
+ for _ in range(x_n * N):
+ rng.uniform(0, 100)
+ rng.seed(SEED)
+ if hasattr(rng, "jumpahead"):
+ rng.jumpahead(rng_offsets.get(fname, 0))
+ inputs = [[rng.uniform(0, 100) for _ in range(N)] for _ in range(x_n)]
+ n = N
+ expected = None
+ # Extract default params from the Python binding
+ params = get_default_params(fname, x_n)
+
+ # Separate params for C subprocess vs Python binding
+ # C subprocess needs original params (may have keys not in binding)
+ # Python binding only accepts keys in its signature
+ binding_params = get_default_params(fname, x_n)
+ if params and binding_params:
+ # Filter to keys that exist in binding
+ filtered = {k: v for k, v in params.items() if k in binding_params}
+ # If no keys match by name, fall back to binding defaults
+ c_params = filtered if filtered else binding_params
+ py_params = filtered if filtered else binding_params
+ elif binding_params:
+ # No parsed params but binding has defaults - use them for both
+ c_params = binding_params
+ py_params = binding_params
+ else:
+ c_params = params
+ py_params = None
+
+ # Call Python binding
+ func = getattr(ts, fname, None)
+ if func is None:
+ skipped += 1
+ print(f" SKIP {fname} (not in Python bindings)")
+ continue
+
+ np_inputs = [np.array(arr, dtype=np.float64) for arr in inputs or []]
+
+ # Call C flat wrapper for non-parameterized functions
+ actual_x_n = len(inputs) if inputs else x_n
+ c_result = call_c_direct(fname, actual_x_n, y_n, inputs, n, c_params)
+
+ try:
+ # Pass params to Python binding if available
+ if py_params:
+ int_params = {}
+ for pk, v in py_params.items():
+ int_params[pk] = int(v) if v == int(v) else v
+ py_result = func(*np_inputs, **int_params)
+ else:
+ py_result = func(*np_inputs)
+ py_result = normalize_result(py_result, y_n, n)
+ except Exception as e:
+ err_msg = str(e)
+ if (
+ re.search(r"ts_\w+\s+error", err_msg)
+ and not binding_params
+ and c_params
+ ):
+ skipped += 1
+ print(f" SKIP {fname} (binding missing params, C needs them)")
+ continue
+ failed += 1
+ print(f" FAIL {fname}: {e}")
+ continue
+
+ # If C comparison unavailable (parameterized function), just verify Python didn't crash
+ if c_result is None:
+ passed += 1
+ print(f" PASS {fname} (py-only)")
+ continue
+
+ # Detect C OOB garbage
+ _min_normal = sys.float_info.min
+
+ def _is_garbage(arr):
+ for v in arr:
+ av = abs(v)
+ if av == 0:
+ continue
+ if 0 < av < _min_normal:
+ return True
+ return False
+
+ c_garbage = any(_is_garbage(r) for r in c_result)
+ if c_garbage:
+ skipped += 1
+ print(f" SKIP {fname} (C OOB garbage)")
+ continue
+
+ # Compare against C output
+ ok = True
+ if len(py_result) != len(c_result):
+ ok = False
+ else:
+ for yi, (got, exp) in enumerate(zip(py_result, c_result)):
+ if not arrays_close(got, exp):
+ ok = False
+ for j, (g, e) in enumerate(zip(got, exp)):
+ if not isclose(g, e):
+ print(f" y[{yi}][{j}]: got={g}, expected={e}")
+ break
+ break
+
+ if ok:
+ passed += 1
+ print(f" PASS {fname}")
+ else:
+ failed += 1
+ print(f" FAIL {fname}")
+
+ # --- Error-case validation (sample) ---
+ print("\n--- Error-case validation ---", flush=True)
+ error_passed = 0
+ error_failed = 0
+ error_sample = sorted(meta.keys())[:20]
+
+ # Test 1: Empty arrays
+ for fname in error_sample:
+ info = meta[fname]
+ x_n = info["x_n"]
+ func = getattr(ts, fname, None)
+ if func is None:
+ continue
+ try:
+ empty_inputs = [np.array([], dtype=np.float64) for _ in range(x_n)]
+ sig = inspect.signature(func)
+ defaults = {
+ k: v.default
+ for k, v in sig.parameters.items()
+ if v.default is not inspect.Parameter.empty
+ and v.annotation is not np.ndarray
+ }
+ func(*empty_inputs, **defaults) if defaults else func(*empty_inputs)
+ error_passed += 1
+ except (RuntimeError, ValueError, IndexError, AssertionError):
+ error_passed += 1
+ except Exception as e:
+ error_failed += 1
+ print(f" ERROR {fname} (empty): {type(e).__name__}: {e}")
+
+ # Test 2: Mismatched array lengths
+ for fname in error_sample:
+ info = meta[fname]
+ x_n = info["x_n"]
+ if x_n < 2:
+ continue
+ func = getattr(ts, fname, None)
+ if func is None:
+ continue
+ try:
+ mismatched = [
+ np.array([1.0] * 10, dtype=np.float64)
+ if i == 0
+ else np.array([1.0] * 5, dtype=np.float64)
+ for i in range(x_n)
+ ]
+ sig = inspect.signature(func)
+ defaults = {
+ k: v.default
+ for k, v in sig.parameters.items()
+ if v.default is not inspect.Parameter.empty
+ and v.annotation is not np.ndarray
+ }
+ func(*mismatched, **defaults) if defaults else func(*mismatched)
+ error_failed += 1
+ print(f" ERROR {fname} (mismatched): should have raised")
+ except (RuntimeError, ValueError, IndexError, AssertionError):
+ error_passed += 1
+ except Exception:
+ error_passed += 1
+
+ # Test 3: NaN/Inf propagation
+ nan_passed = 0
+ nan_failed = 0
+ for fname in error_sample:
+ info = meta[fname]
+ x_n = info["x_n"]
+ func = getattr(ts, fname, None)
+ if func is None:
+ continue
+ try:
+ nan_inputs = [
+ np.array([1.0, float("nan"), 3.0, 4.0, 5.0] * 20, dtype=np.float64)
+ for _ in range(x_n)
+ ]
+ sig = inspect.signature(func)
+ defaults = {
+ k: v.default
+ for k, v in sig.parameters.items()
+ if v.default is not inspect.Parameter.empty
+ and v.annotation is not np.ndarray
+ }
+ func(*nan_inputs, **defaults) if defaults else func(*nan_inputs)
+ nan_passed += 1
+ except RuntimeError:
+ nan_passed += 1
+ except Exception as e:
+ nan_failed += 1
+ print(f" ERROR {fname} (NaN): {type(e).__name__}: {e}")
+
+ total_error = error_passed + error_failed
+ total_nan = nan_passed + nan_failed
+ print(f"\nError-case: {error_passed}/{total_error} passed, {error_failed} failed")
+ print(f"NaN/Inf: {nan_passed}/{total_nan} passed, {nan_failed} failed")
+
+ print(f"\n{passed}/{total} passed, {failed} failed, {skipped} skipped", flush=True)
+ return 0 if failed == 0 else 1
+
+
+if __name__ == "__main__":
+ main()
diff --git a/test/tgamma.c b/test/tgamma.c
new file mode 100644
index 0000000..6e4c54e
--- /dev/null
+++ b/test/tgamma.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_tgamma( void )
+{
+ fp_t const x0[] = { 1.000000, 2.000000, 3.000000, 0.500000, 5.000000 };
+ fp_t const e0[] = { 1.000000, 1.000000, 2.000000, 1.772454, 24.000000 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_tgamma, ts_tgamma_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_tgamma_p);
+}
diff --git a/test/tick_volume.c b/test/tick_volume.c
new file mode 100644
index 0000000..e3055fd
--- /dev/null
+++ b/test/tick_volume.c
@@ -0,0 +1,14 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_tick_volume( void )
+{
+ fp_t const x[] = { 10, 11, 12, 13, 14 };
+ fp_t const e[] = { 1, 1, 1, 1, 1 };
+
+ fp_t const * const inp[1] = { x };
+ fp_t const * const exp[1] = { e };
+
+ return ts_test(ts_tick_volume, ts_tick_volume_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)NULL, &ts_tick_volume_p);
+}
diff --git a/test/time_series_forecast.c b/test/time_series_forecast.c
new file mode 100644
index 0000000..9510879
--- /dev/null
+++ b/test/time_series_forecast.c
@@ -0,0 +1,15 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_time_series_forecast( void )
+{
+ fp_t const x[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+ fp_t const * const inp[1] = { x };
+
+ fp_t const e[] = { 6, 7, 8, 9 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 5;
+ return ts_test(ts_time_series_forecast, ts_time_series_forecast_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_time_series_forecast_p);
+}
diff --git a/test/trade_volume_index.c b/test/trade_volume_index.c
new file mode 100644
index 0000000..b8655d2
--- /dev/null
+++ b/test/trade_volume_index.c
@@ -0,0 +1,29 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_trade_volume_index( void )
+{
+ fp_t const o[] = { 10, 11, 12, 11, 12, 10, 11, 13, 12, 14 };
+ fp_t const h[] = { 12, 13, 14, 12, 14, 11, 12, 15, 14, 16 };
+ fp_t const l[] = { 8, 9, 10, 9, 10, 9, 10, 11, 10, 12 };
+ fp_t const c[] = { 11, 12, 10, 11, 11, 10, 12, 14, 13, 15 };
+ fp_t const v[] = { 100, 200, 300, 400, 500, 300, 200, 400, 300, 500 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t e[10] = { 0 };
+ {
+ fp_t roc = ( c[0] != 0 ) ? ( c[1] - c[0] ) / c[0] : 0;
+ e[0] = roc * v[1];
+ }
+ for( uint_t i = 2; i < 10; ++i )
+ {
+ fp_t roc = ( c[i - 1] != 0 ) ? ( c[i] - c[i - 1] ) / c[i - 1] : 0;
+ e[i - 1] = e[i - 2] + roc * v[i];
+ }
+ e[9] = e[8];
+
+ fp_t const * const e_arr[1] = { e };
+
+ return ts_test(ts_trade_volume_index, ts_trade_volume_index_offset, x, e_arr, 10, (ts_constant_t *)NULL, &ts_trade_volume_index_p);
+}
diff --git a/test/trend_intensity_index.c b/test/trend_intensity_index.c
new file mode 100644
index 0000000..8eca9f3
--- /dev/null
+++ b/test/trend_intensity_index.c
@@ -0,0 +1,41 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_trend_intensity_index( void )
+{
+ uint_t k = 3;
+
+ fp_t const o[] = { 10, 11, 12, 11, 12, 10, 11, 13, 12, 14 };
+ fp_t const h[] = { 12, 13, 14, 12, 14, 11, 12, 15, 14, 16 };
+ fp_t const l[] = { 8, 9, 10, 9, 10, 9, 10, 11, 10, 12 };
+ fp_t const c[] = { 11, 12, 10, 11, 11, 10, 12, 14, 13, 15 };
+ fp_t const v[] = { 100, 200, 300, 400, 500, 300, 200, 400, 300, 500 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t signed_sum = 0;
+ fp_t vol_sum = 0;
+
+ for( uint_t i = 1; i <= k; ++i )
+ {
+ signed_sum += ( c[i] > c[i - 1] ) ? v[i] : -v[i];
+ vol_sum += v[i];
+ }
+
+ fp_t e[10] = { 0 };
+ e[0] = ( vol_sum != 0 ) ? signed_sum / vol_sum : 0;
+
+ for( uint_t i = k + 1; i < 10; ++i )
+ {
+ fp_t cur_signed = ( c[i] > c[i - 1] ) ? v[i] : -v[i];
+ fp_t old_signed = ( c[i - k] > c[i - k - 1] ) ? v[i - k] : -v[i - k];
+ signed_sum += cur_signed - old_signed;
+ vol_sum += v[i] - v[i - k];
+ e[i - (k - 1)] = ( vol_sum != 0 ) ? signed_sum / vol_sum : 0;
+ }
+
+ fp_t const * const e_arr[1] = { e };
+ uint_t c_val = k;
+
+ return ts_test(ts_trend_intensity_index, ts_trend_intensity_index_offset, x, e_arr, 10, (ts_constant_t *)&c_val, &ts_trend_intensity_index_p);
+}
diff --git a/test/trix.c b/test/trix.c
new file mode 100644
index 0000000..29f5451
--- /dev/null
+++ b/test/trix.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_trix( void )
+{
+ fp_t const c[] = { 0.5 };
+
+ fp_t const x0[] = { 1, 2, 3, 4, 5 };
+ fp_t const e0[] = { 1, 0.125, 0.277778, 0.347826, 0.338710 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_trix, ts_trix_offset, x, e, sizeof( c ) / sizeof( *c ), (ts_constant_t *)&c, &ts_trix_p);
+}
diff --git a/test/true_range.c b/test/true_range.c
new file mode 100644
index 0000000..208a8b3
--- /dev/null
+++ b/test/true_range.c
@@ -0,0 +1,18 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_true_range( void )
+{
+ uint_t const c[] = { 1 };
+
+ fp_t const h[] = { 10, 11, 12, 13, 14 };
+ fp_t const l[] = { 8, 9, 10, 11, 12 };
+ fp_t const o[] = { 9, 10, 11, 12, 13 };
+ fp_t const c2[] = { 9.5, 10.5, 11.5, 12.5, 13.5 };
+ fp_t const e0[] = { 2, 2, 2, 2 };
+
+ fp_t const * const x[4] = { o, h, l, c2 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_true_range, ts_true_range_offset, x, e, sizeof( h ) / sizeof( *h ), (ts_constant_t *)&c, &ts_true_range_p);
+}
diff --git a/test/truncate.c b/test/truncate.c
new file mode 100644
index 0000000..44e6ca4
--- /dev/null
+++ b/test/truncate.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_truncate( void )
+{
+ fp_t const x0[] = { -2.7, -1.3, 0, 0.5, 1.3, 2.7, 3.9 };
+ fp_t const e0[] = { -2, -1, 0, 0, 1, 2, 3 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_truncate, ts_truncate_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_truncate_p);
+}
diff --git a/test/twiggs_money_flow.c b/test/twiggs_money_flow.c
new file mode 100644
index 0000000..8fb5471
--- /dev/null
+++ b/test/twiggs_money_flow.c
@@ -0,0 +1,41 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_twiggs_money_flow( void )
+{
+ fp_t x_data[37] = { 0 };
+ for( uint_t i = 0; i < 37; ++i )
+ {
+ x_data[i] = (fp_t)(10 + i);
+ }
+
+ fp_t const * const x[] = { x_data };
+
+ fp_t pos_sum = 0;
+ fp_t neg_sum = 0;
+
+ for( uint_t i = 1; i <= 35; ++i )
+ {
+ fp_t change = x_data[i] - x_data[i - 1];
+ if( change >= 0 ) pos_sum += change;
+ else neg_sum += fabs( change );
+ }
+
+ fp_t e[2] = { 0 };
+ fp_t total = pos_sum + neg_sum;
+ e[0] = ( total != 0 ) ? ( pos_sum - neg_sum ) / total * 100.0 : 0;
+
+ fp_t change = x_data[36] - x_data[35];
+ fp_t old_change = x_data[1] - x_data[0];
+ if( change >= 0 ) pos_sum += change;
+ else neg_sum += fabs( change );
+ if( old_change >= 0 ) pos_sum -= old_change;
+ else neg_sum -= fabs( old_change );
+ total = pos_sum + neg_sum;
+ e[1] = ( total != 0 ) ? ( pos_sum - neg_sum ) / total * 100.0 : 0;
+
+ fp_t const * const e_arr[1] = { e };
+
+ return ts_test(ts_twiggs_money_flow, ts_twiggs_money_flow_offset, x, e_arr, 37, (ts_constant_t *)NULL, &ts_twiggs_money_flow_p);
+}
diff --git a/test/typical_price.c b/test/typical_price.c
new file mode 100644
index 0000000..b43bc3f
--- /dev/null
+++ b/test/typical_price.c
@@ -0,0 +1,17 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_typical_price( void )
+{
+ fp_t const x0[] = { 0, 0, 0, 0 };
+ fp_t const x1[] = { 10, 12, 14, 16 };
+ fp_t const x2[] = { 8, 10, 12, 14 };
+ fp_t const x3[] = { 9, 11, 13, 15 };
+ fp_t const x4[] = { 0, 0, 0, 0 };
+ fp_t const e0[] = { 9, 11, 13, 15 };
+
+ fp_t const * const x[5] = { x0, x1, x2, x3, x4 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_typical_price, ts_typical_price_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_typical_price_p);
+}
diff --git a/test/ulcer_index.c b/test/ulcer_index.c
new file mode 100644
index 0000000..f856723
--- /dev/null
+++ b/test/ulcer_index.c
@@ -0,0 +1,28 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_ulcer_index( void )
+{
+ fp_t const x[] = { 10, 12, 11, 13, 12, 14, 11, 13, 12, 15 };
+ fp_t const * const inp[1] = { x };
+
+ /* k=3, cumulative HHV (running max from index 0) */
+ /* HHV: 10, 12, 12, 13, 13, 14, 14, 14, 14, 15 */
+ /* d%: 0, 0, 8.333, 0, 7.692, 0, 21.429, 7.143, 14.286, 0 */
+ /* d^2: 0, 0, 69.444, 0, 59.172, 0, 459.184, 51.020, 204.082, 0 */
+ /* SMA3 of d^2, then sqrt: */
+ /* i=2: window [0,1,2], d^2=[0,0,69.444], mean=23.148, UI=4.811 */
+ /* i=3: window [1,2,3], d^2=[0,69.444,0], mean=23.148, UI=4.811 */
+ /* i=4: window [2,3,4], d^2=[69.444,0,59.172], mean=42.872, UI=6.548 */
+ /* i=5: window [3,4,5], d^2=[0,59.172,0], mean=19.724, UI=4.441 */
+ /* i=6: window [4,5,6], d^2=[59.172,0,459.184], mean=172.785, UI=13.145 */
+ /* i=7: window [5,6,7], d^2=[0,459.184,51.020], mean=170.069, UI=13.041 */
+ /* i=8: window [6,7,8], d^2=[459.184,51.020,204.082], mean=238.095, UI=15.430 */
+ /* i=9: window [7,8,9], d^2=[51.020,204.082,0], mean=85.034, UI=9.221 */
+ fp_t const e[] = { 4.811252, 4.811252, 6.547672, 4.441156, 13.144774, 13.041013, 15.430335, 9.221389 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_ulcer_index, ts_ulcer_index_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&k, &ts_ulcer_index_p);
+}
diff --git a/test/ultimate_oscillator.c b/test/ultimate_oscillator.c
new file mode 100644
index 0000000..01bdb62
--- /dev/null
+++ b/test/ultimate_oscillator.c
@@ -0,0 +1,90 @@
+#include <stddef.h>
+#include <stdlib.h>
+#include <tgmath.h>
+#include "ts.h"
+
+static fp_t _ts_min( fp_t const * const *x, uint_t start, uint_t count )
+{
+ fp_t m = ts_l( x, start );
+ for( uint_t i = 1; i < count; ++i )
+ {
+ m = fmin( m, ts_l( x, start - i ) );
+ }
+ return m;
+}
+
+static fp_t _ts_max_h( fp_t const * const *x, uint_t start, uint_t count )
+{
+ fp_t m = ts_h( x, start );
+ for( uint_t i = 1; i < count; ++i )
+ {
+ m = fmax( m, ts_h( x, start - i ) );
+ }
+ return m;
+}
+
+bool ts_test_ultimate_oscillator( void )
+{
+ uint_t n = 60;
+
+ fp_t o[60], h[60], l[60], c[60];
+ for (uint_t i = 0; i < n; ++i)
+ {
+ o[i] = 10.0 + (fp_t)i;
+ h[i] = 12.0 + (fp_t)i;
+ l[i] = 8.0 + (fp_t)i;
+ c[i] = 11.0 + (fp_t)i;
+ }
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ uint_t offset = 54;
+ uint_t e_n = n - offset;
+ fp_t *e = calloc(e_n, sizeof(fp_t));
+ if (!e) return false;
+
+ for( uint_t i = offset; i < n; ++i )
+ {
+ fp_t bp7 = 0, tr7 = 0;
+ fp_t bp14 = 0, tr14 = 0;
+ fp_t bp28 = 0, tr28 = 0;
+
+ for( uint_t j = 0; j < 7; ++j )
+ {
+ uint_t idx = i - 6 + j;
+ fp_t ll = _ts_min( x, idx, 7 );
+ fp_t hh = _ts_max_h( x, idx, 7 );
+ bp7 += ts_c( x, idx ) - ll;
+ tr7 += hh - ll;
+ }
+
+ for( uint_t j = 0; j < 14; ++j )
+ {
+ uint_t idx = i - 13 + j;
+ fp_t ll = _ts_min( x, idx, 14 );
+ fp_t hh = _ts_max_h( x, idx, 14 );
+ bp14 += ts_c( x, idx ) - ll;
+ tr14 += hh - ll;
+ }
+
+ for( uint_t j = 0; j < 28; ++j )
+ {
+ uint_t idx = i - 27 + j;
+ fp_t ll = _ts_min( x, idx, 28 );
+ fp_t hh = _ts_max_h( x, idx, 28 );
+ bp28 += ts_c( x, idx ) - ll;
+ tr28 += hh - ll;
+ }
+
+ fp_t denom = 7.0 * tr7 + 3.0 * tr14 + tr28;
+ fp_t numer = 7.0 * bp7 + 3.0 * bp14 + bp28;
+ e[i - offset] = ( denom != 0 ) ? 100.0 * numer / denom : 50.0;
+ }
+
+ fp_t const * const e_arr[1] = { e };
+
+ bool result = ts_test(ts_ultimate_oscillator, ts_ultimate_oscillator_offset, x, e_arr, n, (ts_constant_t *)NULL, &ts_ultimate_oscillator_p);
+
+ free(e);
+ return result;
+}
diff --git a/test/valuation_lines.c b/test/valuation_lines.c
new file mode 100644
index 0000000..10fde35
--- /dev/null
+++ b/test/valuation_lines.c
@@ -0,0 +1,23 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_valuation_lines( void )
+{
+ fp_t const o[] = { 10, 12, 11, 14, 13, 15, 16, 14, 17, 18 };
+ fp_t const h[] = { 12, 14, 13, 16, 15, 17, 18, 16, 19, 20 };
+ fp_t const l[] = { 9, 11, 10, 13, 12, 14, 15, 13, 16, 17 };
+ fp_t const cp[] = { 11, 13, 12, 15, 14, 16, 17, 15, 18, 19 };
+
+ fp_t const * const x[] = { o, h, l, cp };
+
+ /* k=3 */
+ /* LL3: 9,10,10,12,12,13,13,13 */
+ /* HH3: 14,16,16,17,18,18,19,20 */
+ fp_t const es[] = { 9, 10, 10, 12, 12, 13, 13, 13 };
+ fp_t const er[] = { 14, 16, 16, 17, 18, 18, 19, 20 };
+ fp_t const * const exp[2] = { es, er };
+
+ uint_t k = 3;
+ return ts_test(ts_valuation_lines, ts_valuation_lines_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_valuation_lines_p);
+}
diff --git a/test/variable_index_dynamic_average.c b/test/variable_index_dynamic_average.c
new file mode 100644
index 0000000..b7de9f8
--- /dev/null
+++ b/test/variable_index_dynamic_average.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_variable_index_dynamic_average( void )
+{
+ fp_t const x0[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
+ fp_t const e0[] = { 6, 6.375, 6.95052083333333, 7.655029296875, 8.44889958699544, 9.30590988198916, 10.2074478511673 };
+
+ fp_t const * const x[] = { x0 };
+ fp_t const * const e[] = { e0 };
+
+ uint_t c = 5;
+
+ return ts_test(ts_variable_index_dynamic_average, ts_variable_index_dynamic_average_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_variable_index_dynamic_average_p);
+}
diff --git a/test/variance.c b/test/variance.c
new file mode 100644
index 0000000..5dedca5
--- /dev/null
+++ b/test/variance.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_variance( void )
+{
+ uint_t const c[] = { 3 };
+
+ fp_t const x0[] = { 1, 2, 3, 4, 5 };
+ fp_t const e0[] = { 0.666667, 0.666667, 0.666667 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_variance, ts_variance_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_variance_p);
+}
diff --git a/test/variance_exponential.c b/test/variance_exponential.c
new file mode 100644
index 0000000..03e526f
--- /dev/null
+++ b/test/variance_exponential.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_variance_exponential( void )
+{
+ fp_t const c[] = { 0.5 };
+
+ fp_t const x0[] = { 1, 2, 3, 4, 5 };
+ fp_t const e0[] = { 0, 0.25, 0.6875, 1.109375, 1.433594 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_variance_exponential, ts_variance_exponential_offset, x, e, sizeof( c ) / sizeof( *c ), (ts_constant_t *)&c, &ts_variance_exponential_p);
+}
diff --git a/test/vertical_horizontal_filter.c b/test/vertical_horizontal_filter.c
new file mode 100644
index 0000000..2f047ad
--- /dev/null
+++ b/test/vertical_horizontal_filter.c
@@ -0,0 +1,26 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_vertical_horizontal_filter( void )
+{
+ uint_t k = 3;
+
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 14, 13, 15, 16, 14, 17, 18, 16, 19 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
+ fp_t const c[] = { 11, 13, 12, 14, 15, 13, 16, 17, 15, 18 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ /* k=3, offset=3
+ * i=3: hh=15,ll=10,num=5, den=|13-14|+|11-10|+|15-13|+|12-11|+|16-15|+|13-12|=1+1+2+1+1+1=7, VHF=5/7=0.71429
+ * i=4: hh=16,ll=11,num=5, den=|15-13|+|12-11|+|16-15|+|13-12|+|14-16|+|14-13|=2+1+1+1+2+1=8, VHF=5/8=0.625
+ * i=5: hh=16,ll=12,num=4, den=|14-15|+|14-13|+|17-16|+|15-14|+|14-14|+|14-14|=1+1+1+1+0+0=4, VHF=4/4=1.0
+ * ...
+ */
+ fp_t const e[] = { 0.625, 0.71429, 0.5, 0.44444, 0.44444, 0.33333, 0.33333 };
+ fp_t const * const exp[1] = { e };
+
+ return ts_test(ts_vertical_horizontal_filter, ts_vertical_horizontal_filter_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_vertical_horizontal_filter_p);
+}
diff --git a/test/volatility_stop.c b/test/volatility_stop.c
new file mode 100644
index 0000000..59db98f
--- /dev/null
+++ b/test/volatility_stop.c
@@ -0,0 +1,27 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_volatility_stop( void )
+{
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
+ fp_t const cp[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+
+ fp_t const * const x[] = { o, h, l, cp };
+
+ /* k=3, mult=2.0 */
+ /* HH3: 12,13,14,15,16,17,18,19,20,21 */
+ /* LL3: 9,10,11,12,13,14,15,16,17,18 */
+ /* ATR=3.0 */
+ /* LongStop = HH - 2*ATR: 6,7,8,9,10,11,12,13,14 */
+ /* ShortStop = LL + 2*ATR: 15,16,17,18,19,20,21,22,23 */
+ fp_t const longs[] = { 9, 10, 11, 12, 13, 14, 15 };
+ fp_t const shorts[] = { 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const * const exp[2] = { longs, shorts };
+
+ typedef struct { uint_t k; fp_t mult; } c_t;
+ c_t my_c = { .k = 3, .mult = 2.0 };
+ return ts_test(ts_volatility_stop, ts_volatility_stop_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&my_c, &ts_volatility_stop_p);
+}
diff --git a/test/volatility_system.c b/test/volatility_system.c
new file mode 100644
index 0000000..2d940d5
--- /dev/null
+++ b/test/volatility_system.c
@@ -0,0 +1,21 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_volatility_system( void )
+{
+ uint_t k = 3;
+
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+ fp_t const c[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ /* offset = 2k = 6, output starts at index 6 */
+ fp_t const e[] = { 16.5, 17.25, 18.125, 19.0625, 20.03125, 21.015625 };
+ fp_t const * const exp[1] = { e };
+
+ return ts_test(ts_volatility_system, ts_volatility_system_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_volatility_system_p);
+}
diff --git a/test/volume_delta.c b/test/volume_delta.c
new file mode 100644
index 0000000..42a4c87
--- /dev/null
+++ b/test/volume_delta.c
@@ -0,0 +1,39 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_volume_delta( void )
+{
+ fp_t const o[] = { 10, 11, 12, 13, 14 };
+ fp_t const h[] = { 12, 13, 14, 15, 16 };
+ fp_t const l[] = { 8, 9, 10, 11, 12 };
+ fp_t const c[] = { 11, 12, 13, 14, 15 };
+ fp_t const v[] = { 100, 200, 300, 400, 500 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t e[5] = { 0 };
+ for( uint_t i = 0; i < 5; ++i )
+ {
+ fp_t hl = h[i] - l[i];
+ if( hl == 0 )
+ {
+ e[i] = 0;
+ }
+ else if( c[i] > o[i] )
+ {
+ fp_t buy_vol = v[i] * ( c[i] - l[i] ) / hl;
+ fp_t sell_vol = v[i] - buy_vol;
+ e[i] = buy_vol - sell_vol;
+ }
+ else if( c[i] < o[i] )
+ {
+ fp_t sell_vol = v[i] * ( h[i] - c[i] ) / hl;
+ fp_t buy_vol = v[i] - sell_vol;
+ e[i] = buy_vol - sell_vol;
+ }
+ }
+
+ fp_t const * const e_arr[1] = { e };
+
+ return ts_test(ts_volume_delta, ts_volume_delta_offset, x, e_arr, 5, (ts_constant_t *)NULL, &ts_volume_delta_p);
+}
diff --git a/test/volume_moving_average.c b/test/volume_moving_average.c
new file mode 100644
index 0000000..c6338dc
--- /dev/null
+++ b/test/volume_moving_average.c
@@ -0,0 +1,33 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_volume_moving_average( void )
+{
+ uint_t k = TS_TEST_K;
+
+ fp_t const o[] = { 10, 11, 12, 11, 12, 10, 11, 13, 12, 14 };
+ fp_t const h[] = { 12, 13, 14, 12, 14, 11, 12, 15, 14, 16 };
+ fp_t const l[] = { 8, 9, 10, 9, 10, 9, 10, 11, 10, 12 };
+ fp_t const c[] = { 11, 12, 10, 11, 11, 10, 12, 14, 13, 15 };
+ fp_t const v[] = { 100, 200, 300, 400, 500, 300, 200, 400, 300, 500 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t e[10] = { 0 };
+
+ fp_t sum = 0;
+ for( uint_t i = 0; i < k; ++i ) sum += v[i];
+ e[0] = sum / (fp_t)k;
+
+ for( uint_t i = k; i < 10; ++i )
+ {
+ sum += v[i] - v[i - k];
+ e[i - (k - 1)] = sum / (fp_t)k;
+ }
+
+ fp_t const * const e_arr[1] = { e };
+
+ uint_t c_val = k;
+
+ return ts_test(ts_volume_moving_average, ts_volume_moving_average_offset, x, e_arr, 10, (ts_constant_t *)&c_val, &ts_volume_moving_average_p);
+}
diff --git a/test/volume_oscillator.c b/test/volume_oscillator.c
new file mode 100644
index 0000000..daa4bd0
--- /dev/null
+++ b/test/volume_oscillator.c
@@ -0,0 +1,34 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_volume_oscillator( void )
+{
+ fp_t const v[] = { 100, 200, 300, 400, 500 };
+
+ fp_t const * const x[] = { v };
+
+ fp_t fast = 2;
+ fp_t slow = 4;
+ fp_t a_fast = 2.0 / (fast + 1);
+ fp_t a_slow = 2.0 / (slow + 1);
+
+ fp_t ema_f = v[0];
+ fp_t ema_s = v[0];
+ fp_t e[2] = { 0 };
+
+ uint_t idx = 0;
+ for( uint_t i = 1; i < 5; ++i )
+ {
+ ema_f = ema_f + a_fast * ( v[i] - ema_f );
+ ema_s = ema_s + a_slow * ( v[i] - ema_s );
+ if (i >= 3) {
+ e[idx++] = ema_s != 0 ? ( ema_f - ema_s ) / ema_s * 100 : 0;
+ }
+ }
+
+ fp_t const * const e_arr[1] = { e };
+
+ fp_t constants[2] = { fast, slow };
+
+ return ts_test(ts_volume_oscillator, ts_volume_oscillator_offset, x, e_arr, 5, (ts_constant_t *)constants, &ts_volume_oscillator_p);
+}
diff --git a/test/volume_rate_of_change.c b/test/volume_rate_of_change.c
new file mode 100644
index 0000000..f322c69
--- /dev/null
+++ b/test/volume_rate_of_change.c
@@ -0,0 +1,29 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_volume_rate_of_change( void )
+{
+ uint_t k = TS_TEST_K;
+
+ fp_t const o[] = { 10, 11, 12, 11, 12, 10, 11, 13, 12, 14 };
+ fp_t const h[] = { 12, 13, 14, 12, 14, 11, 12, 15, 14, 16 };
+ fp_t const l[] = { 8, 9, 10, 9, 10, 9, 10, 11, 10, 12 };
+ fp_t const c[] = { 11, 12, 10, 11, 11, 10, 12, 14, 13, 15 };
+ fp_t const v[] = { 100, 200, 300, 400, 500, 300, 200, 400, 300, 500 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t e[5];
+
+ for( uint_t i = k; i < 10; ++i )
+ {
+ fp_t v_prev = v[i - k];
+ e[i - k] = v_prev != 0 ? ( v[i] - v_prev ) / v_prev * 100 : 0;
+ }
+
+ fp_t const * const e_arr[1] = { e };
+
+ uint_t c_val = k;
+
+ return ts_test(ts_volume_rate_of_change, ts_volume_rate_of_change_offset, x, e_arr, 10, (ts_constant_t *)&c_val, &ts_volume_rate_of_change_p);
+}
diff --git a/test/volume_underlay.c b/test/volume_underlay.c
new file mode 100644
index 0000000..f02d0c0
--- /dev/null
+++ b/test/volume_underlay.c
@@ -0,0 +1,16 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_volume_underlay( void )
+{
+ fp_t const v[] = { 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000 };
+
+ fp_t const * const x[] = { v };
+
+ fp_t e[] = { 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 };
+
+ fp_t const * const e_arr[1] = { e };
+ uint_t c_val = 1;
+
+ return ts_test(ts_volume_underlay, ts_volume_underlay_offset, x, e_arr, 10, (ts_constant_t *)&c_val, &ts_volume_underlay_p);
+}
diff --git a/test/volume_weighted_moving_average.c b/test/volume_weighted_moving_average.c
new file mode 100644
index 0000000..092d937
--- /dev/null
+++ b/test/volume_weighted_moving_average.c
@@ -0,0 +1,39 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_volume_weighted_moving_average( void )
+{
+ uint_t k = 3;
+
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 };
+ fp_t const c[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+ fp_t const v[] = { 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t cv_sum = 0;
+ fp_t v_sum = 0;
+
+ for( uint_t i = 0; i < k; ++i )
+ {
+ cv_sum += c[i] * v[i];
+ v_sum += v[i];
+ }
+
+ fp_t e[8] = { 0 };
+ e[0] = ( v_sum != 0 ) ? cv_sum / v_sum : 0;
+
+ for( uint_t i = k; i < 10; ++i )
+ {
+ cv_sum += c[i] * v[i] - c[i - k] * v[i - k];
+ v_sum += v[i] - v[i - k];
+ e[i - (k - 1)] = ( v_sum != 0 ) ? cv_sum / v_sum : 0;
+ }
+
+ fp_t const * const e_arr[1] = { e };
+ uint_t c_val = k;
+
+ return ts_test(ts_volume_weighted_moving_average, ts_volume_weighted_moving_average_offset, x, e_arr, 10, (ts_constant_t *)&c_val, &ts_volume_weighted_moving_average_p);
+}
diff --git a/test/vortex_indicator.c b/test/vortex_indicator.c
new file mode 100644
index 0000000..c4e06ba
--- /dev/null
+++ b/test/vortex_indicator.c
@@ -0,0 +1,27 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_vortex_indicator( void )
+{
+ /* k=3 */
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15,16, 17, 18 };
+ fp_t const c[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+
+ fp_t const * const x[] = { o, h, l, c };
+
+ /* VM+(i): |H[i]-L[i-1]| = |13-9|=4, |14-10|=4, |15-11|=4 */
+ /* VM-(i): |L[i]-H[i-1]| = |10-12|=2, |11-13|=2, |12-14|=2 */
+ /* TR(i): max(3,|13-11|=2,|10-11|=1)=3, max(3,|14-12|=2,|11-12|=1)=3, max(3,|15-13|=2,|12-13|=1)=3 */
+ /* SUM_VM+=(1..3) = 12, SUM_VM- = 6, SUM_TR = 9 */
+ /* VI+ = 12/9 = 4/3, VI- = 6/9 = 2/3 */
+ /* All bars same pattern: VI+=4/3, VI-=2/3 */
+ fp_t const evp[] = { 4.0 / 3.0, 4.0 / 3.0, 4.0 / 3.0, 4.0 / 3.0, 4.0 / 3.0, 4.0 / 3.0, 4.0 / 3.0 };
+ fp_t const evm[] = { 2.0 / 3.0, 2.0 / 3.0, 2.0 / 3.0, 2.0 / 3.0, 2.0 / 3.0, 2.0 / 3.0, 2.0 / 3.0 };
+ fp_t const * const exp[2] = { evp, evm };
+
+ uint_t k = 3;
+ return ts_test(ts_vortex_indicator, ts_vortex_indicator_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_vortex_indicator_p);
+}
diff --git a/test/vwap.c b/test/vwap.c
new file mode 100644
index 0000000..c4ad2a3
--- /dev/null
+++ b/test/vwap.c
@@ -0,0 +1,28 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_vwap( void )
+{
+ fp_t const o[] = { 10, 11, 12, 11, 12 };
+ fp_t const h[] = { 12, 13, 14, 12, 14 };
+ fp_t const l[] = { 8, 9, 10, 9, 10 };
+ fp_t const c[] = { 11, 12, 10, 11, 11 };
+ fp_t const v[] = { 100, 200, 300, 400, 500 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t sum_cv = 0;
+ fp_t sum_v = 0;
+ fp_t e[5] = { 0 };
+
+ for( uint_t i = 0; i < 5; ++i )
+ {
+ sum_cv += c[i] * v[i];
+ sum_v += v[i];
+ e[i] = sum_v != 0 ? sum_cv / sum_v : 0;
+ }
+
+ fp_t const * const e_arr[1] = { e };
+
+ return ts_test(ts_vwap, ts_vwap_offset, x, e_arr, 5, (ts_constant_t *)NULL, &ts_vwap_p);
+}
diff --git a/test/vwap_period.c b/test/vwap_period.c
new file mode 100644
index 0000000..d8a9d51
--- /dev/null
+++ b/test/vwap_period.c
@@ -0,0 +1,42 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_vwap_period( void )
+{
+ uint_t k = TS_TEST_K;
+
+ fp_t const o[] = { 10, 11, 12, 11, 12, 10, 11, 13, 12, 14 };
+ fp_t const h[] = { 12, 13, 14, 12, 14, 11, 12, 15, 14, 16 };
+ fp_t const l[] = { 8, 9, 10, 9, 10, 9, 10, 11, 10, 12 };
+ fp_t const c[] = { 11, 12, 10, 11, 11, 10, 12, 14, 13, 15 };
+ fp_t const v[] = { 100, 200, 300, 400, 500, 300, 200, 400, 300, 500 };
+
+ fp_t const * const x[] = { o, h, l, c, v };
+
+ fp_t sum_cv = 0;
+ fp_t sum_v = 0;
+ uint_t count = 0;
+ uint_t offset = k - 1;
+ fp_t e[10] = { 0 };
+
+ for( uint_t i = 0; i < 10; ++i )
+ {
+ sum_cv += c[i] * v[i];
+ sum_v += v[i];
+ count++;
+ if (i >= offset)
+ e[i - offset] = sum_v != 0 ? sum_cv / sum_v : 0;
+ if( count >= k )
+ {
+ sum_cv = 0;
+ sum_v = 0;
+ count = 0;
+ }
+ }
+
+ fp_t const * const e_arr[1] = { e };
+
+ uint_t c_val = k;
+
+ return ts_test(ts_vwap_period, ts_vwap_period_offset, x, e_arr, 10, (ts_constant_t *)&c_val, &ts_vwap_period_p);
+}
diff --git a/test/weibull_pdf.c b/test/weibull_pdf.c
new file mode 100644
index 0000000..04ff453
--- /dev/null
+++ b/test/weibull_pdf.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_weibull_pdf( void )
+{
+ fp_t const c[] = { 2.000000 };
+
+ fp_t const x0[] = { 0.000000, 0.500000, 1.000000, 2.000000, 3.000000 };
+ fp_t const e0[] = { 0.000000, 0.778801, 0.735759, 0.073263, 0.000740 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_weibull_pdf, ts_weibull_pdf_offset, x, e, sizeof( c ) / sizeof( *c ), (ts_constant_t *)&c, &ts_weibull_pdf_p);
+}
diff --git a/test/weighted_close.c b/test/weighted_close.c
new file mode 100644
index 0000000..46084c8
--- /dev/null
+++ b/test/weighted_close.c
@@ -0,0 +1,17 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_weighted_close( void )
+{
+ fp_t const x0[] = { 1, 1, 1, 1, 1 };
+ fp_t const x1[] = { 4, 6, 8, 10, 12 };
+ fp_t const x2[] = { 0, 2, 4, 6, 8 };
+ fp_t const x3[] = { 3, 5, 7, 9, 11 };
+ fp_t const x4[] = { 1, 1, 1, 1, 1 };
+ fp_t const e0[] = { 2.5, 4.5, 6.5, 8.5, 10.5 };
+
+ fp_t const * const x[] = { x0, x1, x2, x3, x4 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_weighted_close, ts_weighted_close_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_weighted_close_p);
+}
diff --git a/test/wilders_smoothing.c b/test/wilders_smoothing.c
new file mode 100644
index 0000000..4bb6037
--- /dev/null
+++ b/test/wilders_smoothing.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_wilders_smoothing( void )
+{
+ fp_t const x0[] = { -10, -7.5, -5, -2.5, 0, 2.5, 5, 7.5, 10 };
+ fp_t const e0[] = { -10, -8.75, -6.875, -4.6875, -2.34375, 0.078125, 2.5390625, 5.01953125, 7.509765625 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ fp_t c = 2;
+
+ return ts_test(ts_wilders_smoothing, ts_wilders_smoothing_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_wilders_smoothing_p);
+}
diff --git a/test/williams_r.c b/test/williams_r.c
new file mode 100644
index 0000000..5d9f32f
--- /dev/null
+++ b/test/williams_r.c
@@ -0,0 +1,22 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_williams_r( void )
+{
+ /* k=3, monotonic increase. C is always at the high end. */
+ /* i=2: HH=14, LL=9, C=13 => -100*(14-13)/(14-9) = -20 */
+ /* i=3: HH=15, LL=10, C=14 => -100*(15-14)/(15-10) = -20 */
+ /* i=4: HH=16, LL=11, C=15 => -20 */
+ fp_t const o[] = { 10, 11, 12, 13, 14, 15, 16, 17 };
+ fp_t const h[] = { 12, 13, 14, 15, 16, 17, 18, 19 };
+ fp_t const l[] = { 9, 10, 11, 12, 13, 14, 15, 16 };
+ fp_t const c[] = { 11, 12, 13, 14, 15, 16, 17, 18 };
+
+ fp_t const * const x[] = { o, h, l, c };
+ fp_t const e[] = { -20, -20, -20, -20, -20, -20 };
+ fp_t const * const exp[1] = { e };
+
+ uint_t k = 3;
+ return ts_test(ts_williams_r, ts_williams_r_offset, x, exp, sizeof( o ) / sizeof( *o ), (ts_constant_t *)&k, &ts_williams_r_p);
+}
diff --git a/test/zero_lag_ema.c b/test/zero_lag_ema.c
new file mode 100644
index 0000000..013c102
--- /dev/null
+++ b/test/zero_lag_ema.c
@@ -0,0 +1,21 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_zero_lag_ema( void )
+{
+ fp_t const x0[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+ fp_t const e0[] = {
+ 6.59259259259259,
+ 7.39506172839506,
+ 8.26337448559671,
+ 9.17558299039781,
+ 10.11705532693187
+ };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ uint_t c = 5;
+
+ return ts_test(ts_zero_lag_ema, ts_zero_lag_ema_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)&c, &ts_zero_lag_ema_p);
+}
diff --git a/test/zeta.c b/test/zeta.c
new file mode 100644
index 0000000..d7282df
--- /dev/null
+++ b/test/zeta.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_zeta( void )
+{
+ fp_t const x0[] = { 2.000000, 3.000000, 4.000000 };
+ fp_t const e0[] = { 1.644934, 1.202057, 1.082323 };
+
+ fp_t const * const x[1] = { x0 };
+ fp_t const * const e[1] = { e0 };
+
+ return ts_test(ts_zeta, ts_zeta_offset, x, e, sizeof( x0 ) / sizeof( *x0 ), (ts_constant_t *)NULL, &ts_zeta_p);
+}
diff --git a/test/zigzag.c b/test/zigzag.c
new file mode 100644
index 0000000..890a1f0
--- /dev/null
+++ b/test/zigzag.c
@@ -0,0 +1,29 @@
+#include <math.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_zigzag( void )
+{
+ /* threshold=5% */
+ /* Prices: 100, 102, 104, 108, 110, 105, 95, 90, 94, 98 */
+ fp_t const x[] = { 100, 102, 104, 108, 110, 105, 95, 90, 94, 98 };
+
+ fp_t const * const inp[1] = { x };
+
+ /* i=0: zz=100, dir=0 */
+ /* i=1: change=(102-100)/100*100=2% < 5%, no change */
+ /* i=2: change=4% < 5%, no change */
+ /* i=3: change=8% >= 5%, zz=108, dir=1 */
+ /* i=4: change=(110-108)/108*100=1.85% < 5%, no change */
+ /* i=5: change=(105-108)/108*100=-2.78% > -5%, no change */
+ /* i=6: change=(95-108)/108*100=-12.04% <= -5%, zz=95, dir=-1 */
+ /* i=7: change=(90-95)/95*100=-5.26% <= -5%, zz=90, dir=-1 */
+ /* i=8: change=(94-90)/90*100=4.44% < 5%, no change */
+ /* i=9: change=(98-90)/90*100=8.89% >= 5%, zz=98, dir=1 */
+ fp_t const ezz[] = { 100, 100, 100, 108, 108, 108, 95, 95, 95, 95 };
+ fp_t const ed[] = { 0, 0, 0, 1, 1, 1, -1, -1, -1, -1 };
+ fp_t const * const exp[2] = { ezz, ed };
+
+ fp_t threshold = 5;
+ return ts_test(ts_zigzag, ts_zigzag_offset, inp, exp, sizeof( x ) / sizeof( *x ), (ts_constant_t *)&threshold, &ts_zigzag_p);
+}
diff --git a/ts.c b/ts.c
new file mode 100644
index 0000000..a85bf0a
--- /dev/null
+++ b/ts.c
@@ -0,0 +1,290 @@
+#include <assert.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "ts.h"
+
+
+/**
+ * Fill an array with linearly spaced values from x0 to x1.
+ * Uses a running accumulator to avoid floating-point drift,
+ * and forces the last element to exactly x1.
+ */
+fp_t* ts_fill(fp_t *x, uint_t n, fp_t x0, fp_t x1 )
+{
+ if( !x || n == 0 ) return NULL;
+ if( n == 1 )
+ {
+ x[0] = x0;
+ return x;
+ }
+
+ /* TS_FILL_ZERO_THRESHOLD avoids division by zero when both endpoints
+ * are effectively zero, and skips unnecessary arithmetic. */
+ if( fabs( x0 ) < TS_FILL_ZERO_THRESHOLD && fabs( x1 ) < TS_FILL_ZERO_THRESHOLD )
+ {
+ for( uint_t i = 0; i < n; i++ )
+ x[i] = 0.0;
+ }
+ else
+ {
+ fp_t step = ( x1 - x0 ) / (fp_t)( n - 1 );
+ fp_t acc = x0;
+ for( uint_t i = 0; i < n - 1; i++ )
+ {
+ x[i] = acc;
+ acc += step;
+ }
+ x[n - 1] = x1;
+ }
+
+ return x;
+}
+
+
+/**
+ * Print an array of values to a stream in bracket notation.
+ * If stream is NULL, output goes to stdout.
+ */
+void ts_print( fp_t const *x, uint_t n, FILE *stream )
+{
+ assert( x != NULL );
+ if( !stream ) stream = stdout;
+ fputc( '[', stream );
+ if( !x || n == 0 )
+ {
+ fputc( ']', stream );
+ return;
+ }
+
+ uint_t i;
+ for( i = 0; i < n - 1; i++ )
+ {
+ fprintf( stream, "%.17g,", x[i] );
+ }
+
+ fprintf( stream, "%.17g]", x[i] );
+}
+
+
+/**
+ * Pretty-print a 2D array of values to a stream.
+ * If stream is NULL, output goes to stdout.
+ */
+void ts_pretty_print( fp_t const * const *x, uint_t m, uint_t n, FILE *stream )
+{
+ if( !stream ) stream = stdout;
+ if( !x || m == 0 || n == 0 )
+ {
+ return;
+ }
+
+ if( m > 1 )
+ {
+ for( uint_t i = 0; i < n; i++ )
+ {
+ for( uint_t j = 0; j < m; j++ )
+ {
+ if( x[j] )
+ fprintf( stream, "%.17g ", x[j][i] );
+ else
+ fprintf( stream, "NULL " );
+ }
+
+ fputs( "\n", stream );
+ }
+ }
+ else
+ {
+ if( !x[0] )
+ {
+ fputs( "[]\n", stream );
+ return;
+ }
+ for( uint_t i = 0; i < n; i++ )
+ {
+ fprintf( stream, "%.17g\n", x[0][i] );
+ }
+ }
+}
+
+
+fp_t _ts_square( fp_t x )
+{
+ return x * x;
+}
+
+
+fp_t _ts_reciprocal( fp_t x )
+{
+ return x != 0 ? 1.0 / x : (fp_t)INFINITY;
+}
+
+
+fp_t _ts_triangle_number( fp_t x )
+{
+ return x * ( x + 1 ) * 0.5;
+}
+
+
+/**
+ * Compute covariance from accumulated sums.
+ * NOTE: This formula is susceptible to catastrophic cancellation for
+ * large or closely clustered inputs. For production use with high
+ * numerical sensitivity, prefer Welford's online algorithm.
+ */
+fp_t _ts_sum_to_covariance( fp_t sum_xy, fp_t sum_x, fp_t sum_y, uint_t k )
+{
+ if( k == 0 )
+ return 0.0;
+ fp_t result = ( sum_xy - ( sum_x * sum_y / k ) ) / k;
+ if( result < 0.0 )
+ result = 0.0;
+ return result;
+}
+
+
+fp_t _ts_sum_to_variance( fp_t sum_x2, fp_t sum_x, uint_t k )
+{
+ return _ts_sum_to_covariance( sum_x2, sum_x, sum_x, k );
+}
+
+
+uint_t _ts_circle_increment( uint_t i, uint_t k )
+{
+ if( k == 0 )
+ return i;
+ return ( i + 1 ) % k;
+}
+
+
+/**
+ * Allocate and zero-initialize a ts_constant_t structure.
+ */
+ts_constant_t* ts_constant( void )
+{
+ return calloc( 1, sizeof( ts_constant_t ) );
+}
+
+
+/**
+ * Free a ts_constant_t structure.
+ */
+void ts_constant_free( ts_constant_t *c )
+{
+ free( c );
+}
+
+
+/**
+ * Run a test harness: call ts_function, then compare outputs against
+ * expected values with tolerance TS_TEST_EPSILON.
+ */
+bool ts_test(
+ int ( *ts_function )(
+ fp_t * const *y,
+ uint_t y_n,
+ fp_t const * const *x,
+ uint_t x_n,
+ uint_t n,
+ ts_constant_t const *c
+ ),
+ uint_t ( *ts_offset )(
+ ts_constant_t const *c
+ ),
+ fp_t const * const *x,
+ fp_t const * const *e,
+ uint_t n,
+ ts_constant_t const *c,
+ ts_parameter_t const *m )
+{
+ if ( !ts_function || !ts_offset )
+ return false;
+
+ if( !m ) return false;
+ if( !e ) return false;
+ if( !x ) return false;
+ if( m->n_constants > 0 && !c ) return false;
+ if( m->n_outputs > 1024 ) return false;
+
+ uint_t offset = ts_offset( c );
+ if ( offset >= n )
+ {
+ return false;
+ }
+ uint_t y_n = n - offset;
+ bool result = true;
+
+ fp_t **y = calloc( m->n_outputs, sizeof( fp_t * ) );
+ if ( !y )
+ {
+ return false;
+ }
+
+ if ( n > UINT64_MAX / sizeof( fp_t ) )
+ {
+ return false;
+ }
+
+ for( uint_t i = 0; i < m->n_outputs; i++ )
+ {
+ fp_t *tmp = calloc( n, sizeof( fp_t ) );
+ if ( !tmp )
+ {
+ for ( uint_t j = 0; j < i; j++ )
+ free( y[j] );
+ free( y );
+ return false;
+ }
+ y[i] = tmp;
+ }
+
+ int ret = ts_function( y, m->n_outputs, x, m->n_inputs, n, c );
+ if ( ret != 0 )
+ {
+ for( uint_t i = 0; i < m->n_outputs; i++ )
+ free( y[i] );
+ free( y );
+ return false;
+ }
+
+ for( uint_t i = 0; i < m->n_outputs; i++ )
+ {
+ for( uint_t j = 0; j < y_n; j++ )
+ {
+ fp_t yv = y[i][j + offset];
+ fp_t ev = e[i][j];
+ if( isnan( yv ) || isnan( ev ) )
+ {
+ fprintf( stderr, "FAIL NaN y[%d][%d]=%f exp=%f\n", (int)i, (int)j, yv, ev );
+ result = false;
+ continue;
+ }
+ /* Matching infinities are equal */
+ if( isinf( yv ) && isinf( ev ) && ( yv > 0 ) == ( ev > 0 ) )
+ continue;
+ if( isinf( yv ) || isinf( ev ) )
+ {
+ fprintf( stderr, "FAIL inf y[%d][%d]=%f exp=%f\n", (int)i, (int)j, yv, ev );
+ result = false;
+ continue;
+ }
+ fp_t diff = fabs( yv - ev );
+ fp_t abs_e = fabs( ev );
+ fp_t tol = TS_TEST_EPSILON + abs_e * TS_TEST_EPSILON;
+ if( diff > tol )
+ fprintf( stderr, "FAIL y[%d][%d]=%.6f exp=%.6f diff=%.8f tol=%.8f\n", (int)i, (int)j, yv, ev, diff, tol );
+ if( diff > tol )
+ result = false;
+ }
+
+ free( y[i] );
+ }
+ free( y );
+
+ return result;
+}
diff --git a/ts.h b/ts.h
new file mode 100644
index 0000000..fdb522e
--- /dev/null
+++ b/ts.h
@@ -0,0 +1,361 @@
+/*
+ * ts — Time Series Analysis Library
+ *
+ * 354 financial and mathematical time-series indicators.
+ * See README.md for the full indicator list and documentation.
+ *
+ * MIT License — Copyright (c) 2025-2026 Ethan Werner
+ */
+
+#ifndef TIME_SERIES_H
+#define TIME_SERIES_H
+
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <inttypes.h>
+#ifndef NDEBUG
+#include <assert.h>
+#endif
+
+
+/*
+ * THREAD SAFETY
+ *
+ * All indicator functions are stateless and thread-safe. Each function operates
+ * solely on its input/output buffers and parameters. Multiple threads may call
+ * indicator functions concurrently provided they use separate buffers.
+ *
+ * ts_constant() / ts_constant_free() use heap allocation and are the caller's
+ * responsibility to manage per-thread.
+ */
+
+/* --- Endianness check --- */
+/* ts_cast_byte macros assume little-endian. Big-endian is unsupported. */
+#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__)
+ #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+ #error "ts requires a little-endian platform. Big-endian is not supported."
+ #endif
+#elif defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)
+ #error "ts requires a little-endian platform. Big-endian is not supported."
+#elif defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN)
+ #error "ts requires a little-endian platform. Big-endian is not supported."
+#endif
+
+
+/* --- API version --- */
+#define TS_VERSION_MAJOR 0
+#define TS_VERSION_MINOR 1
+#define TS_VERSION_PATCH 0
+
+
+/* --- Deprecation --- */
+#ifdef _MSC_VER
+ #define TS_DEPRECATED __declspec(deprecated)
+#else
+ #define TS_DEPRECATED __attribute__((deprecated))
+#endif
+
+
+/* --- Format specifiers --- */
+#define TS_PRIuint PRIu64
+#define TS_PRIfp "lf"
+
+
+typedef double fp_t;
+typedef uint64_t ts_uint_t;
+typedef int64_t ts_int_t;
+
+/* Backward-compatible aliases — prefer ts_uint_t / ts_int_t */
+typedef ts_uint_t uint_t;
+typedef ts_int_t int_t;
+
+
+#define TS_PI 3.14159265358979323846264338327950288419716939937510
+#define TS_HALF_PI 1.57079632679489661923132169163975144209858469968755
+
+/* Backward-compatible aliases */
+#ifndef PI
+#define PI TS_PI
+#endif
+#ifndef HALF_PI
+#define HALF_PI TS_HALF_PI
+#endif
+
+
+/*
+ * OHLCV access macros.
+ * CONTRACT: caller must ensure 0 <= i < n. These macros perform no bounds
+ * checking. n is the data length passed to the indicator function.
+ */
+#define ts_o( x, i ) ( (x)[0][i] )
+#define ts_h( x, i ) ( (x)[1][i] )
+#define ts_l( x, i ) ( (x)[2][i] )
+#define ts_c( x, i ) ( (x)[3][i] )
+#define ts_v( x, i ) ( (x)[4][i] )
+
+
+#ifndef NDEBUG
+/*
+ * Safe OHLCV access macros with bounds checking.
+ * Only active when assertions are enabled (i.e., NDEBUG is not defined).
+ */
+#define ts_o_safe( x, i, n ) ( assert( (i) >= 0 && (uint_t)(i) < (n) ), (x)[0][i] )
+#define ts_h_safe( x, i, n ) ( assert( (i) >= 0 && (uint_t)(i) < (n) ), (x)[1][i] )
+#define ts_l_safe( x, i, n ) ( assert( (i) >= 0 && (uint_t)(i) < (n) ), (x)[2][i] )
+#define ts_c_safe( x, i, n ) ( assert( (i) >= 0 && (uint_t)(i) < (n) ), (x)[3][i] )
+#define ts_v_safe( x, i, n ) ( assert( (i) >= 0 && (uint_t)(i) < (n) ), (x)[4][i] )
+#endif
+
+
+#define TS_TEST_K 5
+#define TS_CONSTANT_SIZE 64
+#define TS_FILL_ZERO_THRESHOLD 1e-15
+#define TS_MAX_LENGTH UINT64_MAX
+
+
+/*
+ * Opaque constant buffer.
+ * MAX PARAMETERS: TS_CONSTANT_SIZE bytes (64). Sufficient for up to 8
+ * uint64_t or 8 fp_t parameters. Larger parameter sets require custom
+ * allocation via ts_constant().
+ * USAGE: pass a pointer to your params struct, e.g.:
+ * uint_t k = 14;
+ * ts_rsi(y, x, n, &k); // &k is cast to ts_constant_t const *
+ * Or use the allocator:
+ * ts_constant_t *c = ts_constant();
+ * *(uint_t *)c = 14;
+ * ts_rsi(y, x, n, c);
+ * ts_constant_free(c);
+ */
+typedef struct ts_constant_s {
+ uint8_t data[TS_CONSTANT_SIZE];
+} ts_constant_t;
+
+
+/*
+ * Parameter metadata for indicator functions.
+ * n_inputs: number of input arrays expected (e.g., 5 for OHLCV)
+ * n_outputs: number of output arrays produced
+ * n_constants: number of constant parameters
+ * params_size: size in bytes of the params struct
+ */
+typedef struct {
+ uint_t n_inputs;
+ uint_t n_outputs;
+ uint_t n_constants;
+ uint_t params_size;
+} ts_parameter_t;
+
+/* Alias for registry introspection — identical layout to ts_parameter_t */
+typedef ts_parameter_t ts_fn_info_t;
+
+uint_t ts_fn_count( void );
+const ts_fn_info_t* ts_registry_get( uint_t index );
+const char* ts_fn_name_get( uint_t index );
+
+
+
+
+
+/*
+ * TS_PROLOGUE — Common validation for all indicator functions.
+ * USAGE: Place at the top of the function body.
+ * TS_PROLOGUE( ts_rsi_p )
+ * Checks pointers, data length, and input/output array counts against
+ * the indicator's ts_parameter_t metadata. Does NOT check `c` or inputs
+ * — do that explicitly if your indicator requires them.
+ */
+#define TS_PROLOGUE( _p ) \
+ do { \
+ if ( !x || !y || n == 0 || \
+ x_n < (_p).n_inputs || y_n < (_p).n_outputs ) return -1; \
+ } while ( 0 )
+
+#define TS_TEST_EPSILON 1.0e-5
+
+/*
+ * TS_CHECK_INPUT — Validate all input arrays for NaN/Inf.
+ * USAGE: Place at the start of the function body.
+ * TS_CHECK_INPUT(x, x_n, n)
+ * Checks up to the first `n` elements of each of the `x_n` input arrays
+ * for NaN or Inf. Returns -1 if any invalid value found.
+ */
+#define TS_CHECK_INPUT( x, x_n, n ) \
+ do { \
+ for ( uint_t _ts_c = 0; _ts_c < (x_n); ++_ts_c ) { \
+ for ( uint_t _ts_i = 0; _ts_i < (n); ++_ts_i ) { \
+ if ( isnan( (x)[_ts_c][_ts_i] ) || isinf( (x)[_ts_c][_ts_i] ) ) return -1; \
+ } \
+ } \
+ } while ( 0 )
+
+
+/* --- Test / dev utilities (only when TS_IMPLEMENTATION is defined) --- */
+
+/*
+ * ts_cast_byte — Read a value of the given type from a byte offset in data.
+ * NOTE: assumes little-endian byte order. Behavior is undefined on
+ * big-endian platforms without adjustment.
+ */
+#define ts_cast_byte( data, byte, type ) \
+ _ts_read_##type( ( const uint8_t * )( data ) + ( byte ) )
+
+
+static inline uint_t _ts_read_uint_t( const uint8_t *p )
+{
+ uint_t v;
+ memcpy( &v, p, sizeof( v ) );
+ return v;
+}
+
+
+static inline fp_t _ts_read_fp_t( const uint8_t *p )
+{
+ fp_t v;
+ memcpy( &v, p, sizeof( v ) );
+ return v;
+}
+
+
+static inline int_t _ts_read_int_t( const uint8_t *p )
+{
+ int_t v;
+ memcpy( &v, p, sizeof( v ) );
+ return v;
+}
+
+
+/*
+ * ts_fill — Linearly fill array x with values from x0 to x1.
+ * RETURNS: the same pointer x (for convenience). Caller retains ownership.
+ * PRECONDITION: x must not be NULL; n > 0.
+ */
+fp_t * ts_fill( fp_t *x, uint_t n, fp_t x0, fp_t x1 );
+
+/*
+ * ts_print — Print n elements of x to stream.
+ * If stream is NULL, output goes to stdout.
+ * PRECONDITION: x must not be NULL.
+ */
+void ts_print( fp_t const *x, uint_t n, FILE *stream );
+
+/*
+ * ts_pretty_print — Pretty-print m arrays of n elements each to stream.
+ * If stream is NULL, output goes to stdout.
+ * PRECONDITION: x must not be NULL.
+ */
+void ts_pretty_print( fp_t const * const *x, uint_t m, uint_t n, FILE *stream );
+
+
+/*
+ * ts_constant — Allocate a zero-initialized constant buffer on the heap.
+ * RETURNS: heap-allocated ts_constant_t*, or NULL on allocation failure.
+ * CALLER MUST free with ts_constant_free().
+ */
+ts_constant_t* ts_constant( void );
+
+/*
+ * ts_constant_free — Free a constant buffer allocated by ts_constant().
+ * NULL-safe: passing NULL is a no-op.
+ */
+void ts_constant_free( ts_constant_t *c );
+/* Internal helpers (underscore prefix indicates non-public API) */
+uint_t _ts_circle_increment( uint_t, uint_t );
+fp_t _ts_square( fp_t );
+fp_t _ts_reciprocal( fp_t );
+fp_t _ts_triangle_number( fp_t );
+fp_t _ts_sum_to_covariance( fp_t, fp_t, fp_t, uint_t );
+fp_t _ts_sum_to_variance( fp_t, fp_t, uint_t );
+
+/*
+ * Welford's online variance accumulator (numerically stable).
+ * Fields are public for direct access; initialize with ts_welford_init().
+ * PRECONDITION: pointer w must not be NULL for all ts_welford_* functions.
+ */
+typedef struct {
+ fp_t mean;
+ fp_t m2;
+ uint_t count;
+} ts_welford_t;
+
+static inline void ts_welford_init( ts_welford_t *w )
+{
+ if (!w) return;
+ w->mean = 0; w->m2 = 0; w->count = 0;
+}
+
+static inline void ts_welford_add( ts_welford_t *w, fp_t x )
+{
+ if (!w) return;
+ w->count++;
+ fp_t delta = x - w->mean;
+ w->mean += delta / w->count;
+ fp_t delta2 = x - w->mean;
+ w->m2 += delta * delta2;
+}
+
+static inline void ts_welford_remove( ts_welford_t *w, fp_t x )
+{
+ if (!w) return;
+ if (w->count <= 1) { ts_welford_init(w); return; }
+ fp_t old_mean = w->mean;
+ w->count--;
+ w->mean = (old_mean * (w->count + 1) - x) / w->count;
+ fp_t delta = x - old_mean;
+ fp_t delta2 = x - w->mean;
+ w->m2 -= delta * delta2;
+ if (w->m2 < 0) w->m2 = 0;
+}
+
+static inline fp_t ts_welford_variance( ts_welford_t *w )
+{
+ if (!w) return 0;
+ return w->count > 0 ? w->m2 / w->count : 0;
+}
+
+
+/* --- Test harness (only when TS_IMPLEMENTATION is defined) --- */
+#ifdef TS_IMPLEMENTATION
+
+/*
+ * ts_test — Run an indicator function and compare output against expected values.
+ *
+ * CONTRACT: each expected array e[i] must have at least (n - offset) elements,
+ * where offset = ts_offset(c). The test compares y[i][j + offset] against e[i][j]
+ * for j in [0, n - offset).
+ */
+bool ts_test(
+ int ( *function )(
+ fp_t * const *y,
+ uint_t y_n,
+ fp_t const * const *x,
+ uint_t x_n,
+ uint_t n,
+ ts_constant_t const *c
+ ),
+ uint_t ( *ts_offset )(
+ ts_constant_t const *c
+ ),
+ fp_t const * const *x,
+ fp_t const * const *e,
+ uint_t n,
+ ts_constant_t const *c,
+ ts_parameter_t const *m );
+
+#endif /* TS_IMPLEMENTATION */
+
+/*
+ * Auto-generated indicator declarations.
+ * Generated by scripts/gen_all.py → scripts/gen_bindings.py.
+ * Do not edit manually.
+ * NOTE: ts_generated.h must exist before including this header.
+ * Generate with: python3 scripts/gen_all.py
+ */
+#include "ts_generated.h"
+
+#endif /* TIME_SERIES_H */
diff --git a/ts.pc.in b/ts.pc.in
new file mode 100644
index 0000000..5545454
--- /dev/null
+++ b/ts.pc.in
@@ -0,0 +1,11 @@
+prefix=@PREFIX@
+exec_prefix=${prefix}
+libdir=${exec_prefix}/lib
+includedir=${prefix}/include
+
+Name: ts
+Description: Time series analysis library
+Version: @VERSION@
+Libs: -L${libdir} -lts
+Libs.private: -lm
+Cflags: -I${includedir}/ts