Benchmark Results¶
tulip_rs is benchmarked against three established technical-analysis libraries across four benchmark suites.
TulipRS vs TA-Lib, C Tulip, pandas-ta, and more¶
- Up to 20,802× faster than pandas-based Python TA libraries (
nvi), with a ~22× median speedup across 35 commonly used indicators - Up to 11.93× faster than TA-Lib (
hilberttransform), with wins on 95%+ of directly comparable indicators - Beats every pure-Rust TA-Lib-inspired competitor on 100% of compared
indicators — up to 36.10× faster than
kand(mfi) and up to 6.78× faster thanRustTa(ef) - ~46× faster than
technicalindicators(Node.js/TypeScript), median across 41 indicators — up to 403× forwma - Up to 7,491× faster than pure-Go TA libraries (
cinar/indicator), with a ~2,283× median speedup across 45 compared indicators - SIMD batch mode delivers up to 3.85× extra throughput on top of already-fast scalar Rust, when computing multiple assets or parameter sets at once
- ~27 ns median per-bar streaming update — 100–5,489× faster than recomputing from scratch on every new bar
(All numbers below are reproducible — see Methodology.)
Competitors¶
| Library | Language | Description |
|---|---|---|
| Tulip Indicators (C) | C | The C library that inspired tulip_rs; primary scalar baseline |
| TA-Lib | C | Industry-standard C technical-analysis library |
| kand | Rust | Pure-Rust TA-Lib–inspired library; 36 indicators benchmarked |
| RustTa | Rust | Rust implementation with streaming support; 20 indicators benchmarked |
| technicalindicators | JavaScript/TypeScript | Popular JS/TS TA library; primary Node.js binding benchmark reference |
| indicatorts | TypeScript | Pure-TS TA library; second Node.js binding benchmark reference |
All timings are nanoseconds (ns) — lower is better. Ratios > 1.00 mean Rust is faster than the competitor.
Benchmark Suites¶
| Suite | Description |
|---|---|
| Standard Performance | Single-asset throughput averaged across 4 option sets, vs C Tulip / RustTa / TA-Lib |
| SIMD | 4-asset parallel (by_assets) and 4-option-set parallel (by_options) vectorised performance |
| Optional Outputs | Single-pass computation advantage for indicators with sub-indicator outputs |
| Streaming | Per-bar stateful update performance vs full batch recomputation |
| Python Binding | tulip_rs_python (PyO3) vs ta (pandas-based) across 35 indicators |
| Node Binding | tulip_rs_node (napi-rs) vs technicalindicators and indicatorts across 41 indicators |
Methodology¶
| Item | Detail |
|---|---|
| Data source | indicator_benchmark PostgreSQL database — latest run per indicator |
| Input data | Real OHLCV market data — 6,705 bars per asset (single ticker) |
| Options | Each indicator is run across 4 option sets (see reference below); reported time is the average across all sets |
| Build flags | -C opt-level=3 -C lto=fat -C target-cpu=native |
| Benchmark harness | Criterion.rs |
| SIMD lane count | N = 4 (256-bit AVX2 f64x4) |
| Comparison method | C libraries called through native FFI; no wrappers inside the timed region |
| Warm-up | Criterion warm-up phase runs before measurement to stabilise CPU frequency and cache state |
N/A entries
"N/A" in a competitor column means that library does not implement the indicator. "—" in a ratio column means the ratio is not applicable (library absent or Rust is the only implementation).
RustTa streaming
RustTa processes data one bar at a time (streaming) so its timings reflect full 6,705-bar throughput across sequential single-bar calls. See Streaming for tulip_rs streaming performance.
Benchmark options reference — all indicators
Times are averaged across the following 4 option sets per indicator. Indicators with no options run a single configuration.
| Indicator | Option sets used (averaged) |
|---|---|
ad |
(no options) |
adosc |
(short=2, long=5) · (short=6, long=20) · (short=5, long=15) · (short=10, long=30) |
adx |
period: 5 · 14 · 24 · 30 |
adxr |
period: 5 · 14 · 24 · 30 |
ao |
(no options) |
apo |
(short=2, long=5) · (short=11, long=21) · (short=5, long=11) · (short=14, long=30) |
aroon |
period: 25 · 35 · 50 · 100 |
aroonosc |
period: 25 · 35 · 50 · 100 |
atr |
period: 5 · 14 · 25 · 30 |
avgprice |
(no options) |
bbands |
(period=5, mult=2) · (period=14, mult=2) · (period=20, mult=2) · (period=50, mult=2) |
bop |
(no options) |
cci |
period: 20 · 25 · 30 · 50 |
chaikinmf |
period: 5 · 14 · 20 · 30 |
chandelierexit |
(period=5, step=2) · (period=14, step=2) · (period=20, step=2) · (period=30, step=2) |
cmo |
period: 5 · 14 · 20 · 30 |
cvi |
period: 5 · 14 · 20 · 30 |
dema |
period: 5 · 14 · 20 · 50 |
di |
period: 5 · 14 · 20 · 30 |
dm |
period: 24 · 14 · 5 · 30 |
donchianchannel |
period: 25 · 35 · 50 · 100 |
dpo |
period: 5 · 14 · 20 · 30 |
dx |
period: 24 · 14 · 5 · 30 |
ef |
period: 5 · 14 · 20 · 30 |
elderray |
period: 5 · 14 · 20 · 30 |
ema |
period: 14 · 20 · 26 · 50 |
emv |
(no options) |
fisher |
period: 25 · 35 · 50 · 100 |
fosc |
period: 5 · 14 · 20 · 25 |
hma |
period: 5 · 14 · 20 · 50 |
kama |
period: 5 · 10 · 14 · 20 |
keltnerchannel |
(period=5, step=2) · (period=14, step=2) · (period=20, step=2) · (period=30, step=2) |
kvo |
(short=2, long=5) · (short=9, long=26) · (short=14, long=30) · (short=20, long=50) |
linreg |
period: 5 · 14 · 20 · 25 |
macd |
(fast=5, slow=13, signal=8) · (fast=19, slow=39, signal=9) · (fast=10, slow=30, signal=10) · (fast=6, slow=20, signal=9) |
marketfi |
(no options) |
mass |
period: 25 · 30 · 50 · 100 |
max |
period: 25 · 35 · 50 · 100 |
md |
period: 5 · 10 · 14 · 25 |
medprice |
(no options) |
mfi |
period: 14 · 20 · 25 · 30 |
min |
period: 25 · 35 · 50 · 100 |
mom |
period: 25 · 30 · 50 · 100 |
msw |
period: 20 · 25 · 30 · 50 |
natr |
period: 14 · 20 · 25 · 30 |
nvi |
(no options) |
obv |
(no options) |
ppo |
(short=2, long=5) · (short=12, long=26) · (short=9, long=20) · (short=8, long=18) |
psar |
(step=0.02, max=0.2) · (step=0.2, max=2.0) · (step=0.04, max=0.4) · (step=0.4, max=4.0) |
pvi |
(no options) |
qstick |
period: 5 · 2 · 8 · 14 |
roc |
period: 25 · 30 · 50 · 100 |
rocr |
period: 25 · 30 · 50 · 100 |
rsi |
period: 14 · 20 · 25 · 30 |
sma |
period: 50 · 100 · 200 · 300 |
smaenvelope |
(period=50, pct=2) · (period=100, pct=2) · (period=200, pct=2) · (period=300, pct=2) |
stddev |
period: 20 · 50 · 100 · 200 |
stoch |
(k=28, ks=16, d=12) · (k=35, ks=21, d=14) · (k=50, ks=30, d=21) · (k=100, ks=50, d=30) |
stochrsi |
period: 14 · 20 · 25 · 35 |
tema |
period: 5 · 14 · 20 · 50 |
tr |
(no options) |
trima |
period: 14 · 20 · 25 · 30 |
trvi |
period: 5 · 14 · 20 · 30 |
trix |
period: 14 · 15 · 20 · 30 |
tsf |
period: 5 · 14 · 20 · 25 |
typprice |
(no options) |
ultosc |
(s=2, m=3, l=5) · (s=10, m=14, l=20) · (s=14, m=20, l=50) · (s=20, m=50, l=100) |
vhf |
period: 25 · 35 · 50 · 100 |
vidya |
(short=9, long=12, α=0.2) · (short=12, long=26, α=0.2) · (short=14, long=30, α=0.2) · (short=14, long=30, α=0.4) |
volatility |
period: 14 · 20 · 25 · 30 |
vortex |
period: 5 · 14 · 20 · 30 |
vosc |
(short=2, long=5) · (short=5, long=20) · (short=10, long=25) · (short=14, long=28) |
vwma |
period: 14 · 20 · 25 · 30 |
wad |
(no options) |
wcprice |
(no options) |
wilders |
period: 20 · 25 · 30 · 50 |
willr |
period: 25 · 35 · 50 · 100 |
wma |
period: 14 · 20 · 25 · 30 |
zlema |
period: 5 · 10 · 14 · 20 |
Combined Advantage: a Worked Example¶
Consider computing tema with all its sub-indicators (dema, ema) across 4 assets simultaneously with SIMD:
| Implementation | Time |
|---|---|
tulip_rs SIMD 4-asset (all outputs, one pass) |
8,099 ns |
| C Tulip — 4 × (tema + dema + ema) sequential | 94,408 ns |
| TA-Lib — 4 × (tema + dema + ema) sequential | 258,992 ns |
Combined speedup
~11.7× faster than C Tulip and ~32.0× faster than TA-Lib for the same result.