From 07e18043145f3d91c8ea7ec3ad41b3c905585277 Mon Sep 17 00:00:00 2001 From: Ethan Werner Date: Wed, 1 Jul 2026 09:42:39 +0400 Subject: 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 --- ts.h | 361 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 361 insertions(+) create mode 100644 ts.h (limited to 'ts.h') diff --git a/ts.h b/ts.h new file mode 100644 index 0000000..fdb522e --- /dev/null +++ b/ts.h @@ -0,0 +1,361 @@ +/* + * ts — Time Series Analysis Library + * + * 354 financial and mathematical time-series indicators. + * See README.md for the full indicator list and documentation. + * + * MIT License — Copyright (c) 2025-2026 Ethan Werner + */ + +#ifndef TIME_SERIES_H +#define TIME_SERIES_H + + +#include +#include +#include +#include +#include +#include +#include +#ifndef NDEBUG +#include +#endif + + +/* + * THREAD SAFETY + * + * All indicator functions are stateless and thread-safe. Each function operates + * solely on its input/output buffers and parameters. Multiple threads may call + * indicator functions concurrently provided they use separate buffers. + * + * ts_constant() / ts_constant_free() use heap allocation and are the caller's + * responsibility to manage per-thread. + */ + +/* --- Endianness check --- */ +/* ts_cast_byte macros assume little-endian. Big-endian is unsupported. */ +#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) + #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + #error "ts requires a little-endian platform. Big-endian is not supported." + #endif +#elif defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__) + #error "ts requires a little-endian platform. Big-endian is not supported." +#elif defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN) + #error "ts requires a little-endian platform. Big-endian is not supported." +#endif + + +/* --- API version --- */ +#define TS_VERSION_MAJOR 0 +#define TS_VERSION_MINOR 1 +#define TS_VERSION_PATCH 0 + + +/* --- Deprecation --- */ +#ifdef _MSC_VER + #define TS_DEPRECATED __declspec(deprecated) +#else + #define TS_DEPRECATED __attribute__((deprecated)) +#endif + + +/* --- Format specifiers --- */ +#define TS_PRIuint PRIu64 +#define TS_PRIfp "lf" + + +typedef double fp_t; +typedef uint64_t ts_uint_t; +typedef int64_t ts_int_t; + +/* Backward-compatible aliases — prefer ts_uint_t / ts_int_t */ +typedef ts_uint_t uint_t; +typedef ts_int_t int_t; + + +#define TS_PI 3.14159265358979323846264338327950288419716939937510 +#define TS_HALF_PI 1.57079632679489661923132169163975144209858469968755 + +/* Backward-compatible aliases */ +#ifndef PI +#define PI TS_PI +#endif +#ifndef HALF_PI +#define HALF_PI TS_HALF_PI +#endif + + +/* + * OHLCV access macros. + * CONTRACT: caller must ensure 0 <= i < n. These macros perform no bounds + * checking. n is the data length passed to the indicator function. + */ +#define ts_o( x, i ) ( (x)[0][i] ) +#define ts_h( x, i ) ( (x)[1][i] ) +#define ts_l( x, i ) ( (x)[2][i] ) +#define ts_c( x, i ) ( (x)[3][i] ) +#define ts_v( x, i ) ( (x)[4][i] ) + + +#ifndef NDEBUG +/* + * Safe OHLCV access macros with bounds checking. + * Only active when assertions are enabled (i.e., NDEBUG is not defined). + */ +#define ts_o_safe( x, i, n ) ( assert( (i) >= 0 && (uint_t)(i) < (n) ), (x)[0][i] ) +#define ts_h_safe( x, i, n ) ( assert( (i) >= 0 && (uint_t)(i) < (n) ), (x)[1][i] ) +#define ts_l_safe( x, i, n ) ( assert( (i) >= 0 && (uint_t)(i) < (n) ), (x)[2][i] ) +#define ts_c_safe( x, i, n ) ( assert( (i) >= 0 && (uint_t)(i) < (n) ), (x)[3][i] ) +#define ts_v_safe( x, i, n ) ( assert( (i) >= 0 && (uint_t)(i) < (n) ), (x)[4][i] ) +#endif + + +#define TS_TEST_K 5 +#define TS_CONSTANT_SIZE 64 +#define TS_FILL_ZERO_THRESHOLD 1e-15 +#define TS_MAX_LENGTH UINT64_MAX + + +/* + * Opaque constant buffer. + * MAX PARAMETERS: TS_CONSTANT_SIZE bytes (64). Sufficient for up to 8 + * uint64_t or 8 fp_t parameters. Larger parameter sets require custom + * allocation via ts_constant(). + * USAGE: pass a pointer to your params struct, e.g.: + * uint_t k = 14; + * ts_rsi(y, x, n, &k); // &k is cast to ts_constant_t const * + * Or use the allocator: + * ts_constant_t *c = ts_constant(); + * *(uint_t *)c = 14; + * ts_rsi(y, x, n, c); + * ts_constant_free(c); + */ +typedef struct ts_constant_s { + uint8_t data[TS_CONSTANT_SIZE]; +} ts_constant_t; + + +/* + * Parameter metadata for indicator functions. + * n_inputs: number of input arrays expected (e.g., 5 for OHLCV) + * n_outputs: number of output arrays produced + * n_constants: number of constant parameters + * params_size: size in bytes of the params struct + */ +typedef struct { + uint_t n_inputs; + uint_t n_outputs; + uint_t n_constants; + uint_t params_size; +} ts_parameter_t; + +/* Alias for registry introspection — identical layout to ts_parameter_t */ +typedef ts_parameter_t ts_fn_info_t; + +uint_t ts_fn_count( void ); +const ts_fn_info_t* ts_registry_get( uint_t index ); +const char* ts_fn_name_get( uint_t index ); + + + + + +/* + * TS_PROLOGUE — Common validation for all indicator functions. + * USAGE: Place at the top of the function body. + * TS_PROLOGUE( ts_rsi_p ) + * Checks pointers, data length, and input/output array counts against + * the indicator's ts_parameter_t metadata. Does NOT check `c` or inputs + * — do that explicitly if your indicator requires them. + */ +#define TS_PROLOGUE( _p ) \ + do { \ + if ( !x || !y || n == 0 || \ + x_n < (_p).n_inputs || y_n < (_p).n_outputs ) return -1; \ + } while ( 0 ) + +#define TS_TEST_EPSILON 1.0e-5 + +/* + * TS_CHECK_INPUT — Validate all input arrays for NaN/Inf. + * USAGE: Place at the start of the function body. + * TS_CHECK_INPUT(x, x_n, n) + * Checks up to the first `n` elements of each of the `x_n` input arrays + * for NaN or Inf. Returns -1 if any invalid value found. + */ +#define TS_CHECK_INPUT( x, x_n, n ) \ + do { \ + for ( uint_t _ts_c = 0; _ts_c < (x_n); ++_ts_c ) { \ + for ( uint_t _ts_i = 0; _ts_i < (n); ++_ts_i ) { \ + if ( isnan( (x)[_ts_c][_ts_i] ) || isinf( (x)[_ts_c][_ts_i] ) ) return -1; \ + } \ + } \ + } while ( 0 ) + + +/* --- Test / dev utilities (only when TS_IMPLEMENTATION is defined) --- */ + +/* + * ts_cast_byte — Read a value of the given type from a byte offset in data. + * NOTE: assumes little-endian byte order. Behavior is undefined on + * big-endian platforms without adjustment. + */ +#define ts_cast_byte( data, byte, type ) \ + _ts_read_##type( ( const uint8_t * )( data ) + ( byte ) ) + + +static inline uint_t _ts_read_uint_t( const uint8_t *p ) +{ + uint_t v; + memcpy( &v, p, sizeof( v ) ); + return v; +} + + +static inline fp_t _ts_read_fp_t( const uint8_t *p ) +{ + fp_t v; + memcpy( &v, p, sizeof( v ) ); + return v; +} + + +static inline int_t _ts_read_int_t( const uint8_t *p ) +{ + int_t v; + memcpy( &v, p, sizeof( v ) ); + return v; +} + + +/* + * ts_fill — Linearly fill array x with values from x0 to x1. + * RETURNS: the same pointer x (for convenience). Caller retains ownership. + * PRECONDITION: x must not be NULL; n > 0. + */ +fp_t * ts_fill( fp_t *x, uint_t n, fp_t x0, fp_t x1 ); + +/* + * ts_print — Print n elements of x to stream. + * If stream is NULL, output goes to stdout. + * PRECONDITION: x must not be NULL. + */ +void ts_print( fp_t const *x, uint_t n, FILE *stream ); + +/* + * ts_pretty_print — Pretty-print m arrays of n elements each to stream. + * If stream is NULL, output goes to stdout. + * PRECONDITION: x must not be NULL. + */ +void ts_pretty_print( fp_t const * const *x, uint_t m, uint_t n, FILE *stream ); + + +/* + * ts_constant — Allocate a zero-initialized constant buffer on the heap. + * RETURNS: heap-allocated ts_constant_t*, or NULL on allocation failure. + * CALLER MUST free with ts_constant_free(). + */ +ts_constant_t* ts_constant( void ); + +/* + * ts_constant_free — Free a constant buffer allocated by ts_constant(). + * NULL-safe: passing NULL is a no-op. + */ +void ts_constant_free( ts_constant_t *c ); +/* Internal helpers (underscore prefix indicates non-public API) */ +uint_t _ts_circle_increment( uint_t, uint_t ); +fp_t _ts_square( fp_t ); +fp_t _ts_reciprocal( fp_t ); +fp_t _ts_triangle_number( fp_t ); +fp_t _ts_sum_to_covariance( fp_t, fp_t, fp_t, uint_t ); +fp_t _ts_sum_to_variance( fp_t, fp_t, uint_t ); + +/* + * Welford's online variance accumulator (numerically stable). + * Fields are public for direct access; initialize with ts_welford_init(). + * PRECONDITION: pointer w must not be NULL for all ts_welford_* functions. + */ +typedef struct { + fp_t mean; + fp_t m2; + uint_t count; +} ts_welford_t; + +static inline void ts_welford_init( ts_welford_t *w ) +{ + if (!w) return; + w->mean = 0; w->m2 = 0; w->count = 0; +} + +static inline void ts_welford_add( ts_welford_t *w, fp_t x ) +{ + if (!w) return; + w->count++; + fp_t delta = x - w->mean; + w->mean += delta / w->count; + fp_t delta2 = x - w->mean; + w->m2 += delta * delta2; +} + +static inline void ts_welford_remove( ts_welford_t *w, fp_t x ) +{ + if (!w) return; + if (w->count <= 1) { ts_welford_init(w); return; } + fp_t old_mean = w->mean; + w->count--; + w->mean = (old_mean * (w->count + 1) - x) / w->count; + fp_t delta = x - old_mean; + fp_t delta2 = x - w->mean; + w->m2 -= delta * delta2; + if (w->m2 < 0) w->m2 = 0; +} + +static inline fp_t ts_welford_variance( ts_welford_t *w ) +{ + if (!w) return 0; + return w->count > 0 ? w->m2 / w->count : 0; +} + + +/* --- Test harness (only when TS_IMPLEMENTATION is defined) --- */ +#ifdef TS_IMPLEMENTATION + +/* + * ts_test — Run an indicator function and compare output against expected values. + * + * CONTRACT: each expected array e[i] must have at least (n - offset) elements, + * where offset = ts_offset(c). The test compares y[i][j + offset] against e[i][j] + * for j in [0, n - offset). + */ +bool ts_test( + int ( *function )( + fp_t * const *y, + uint_t y_n, + fp_t const * const *x, + uint_t x_n, + uint_t n, + ts_constant_t const *c + ), + uint_t ( *ts_offset )( + ts_constant_t const *c + ), + fp_t const * const *x, + fp_t const * const *e, + uint_t n, + ts_constant_t const *c, + ts_parameter_t const *m ); + +#endif /* TS_IMPLEMENTATION */ + +/* + * Auto-generated indicator declarations. + * Generated by scripts/gen_all.py → scripts/gen_bindings.py. + * Do not edit manually. + * NOTE: ts_generated.h must exist before including this header. + * Generate with: python3 scripts/gen_all.py + */ +#include "ts_generated.h" + +#endif /* TIME_SERIES_H */ -- cgit v1.3.1