summaryrefslogtreecommitdiffstats
path: root/lib
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 /lib
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
Diffstat (limited to 'lib')
-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
351 files changed, 20008 insertions, 0 deletions
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;
+}
+