Getting Started¶
Installation¶
Add TulipRS to your Cargo.toml:
To get the very latest unreleased changes, use the Git source directly:
TulipRS uses the portable_simd nightly language feature internally and requires a nightly Rust toolchain. The correct nightly version is pinned automatically via the rust-toolchain.toml file at the root of the repository — no manual toolchain management is needed.
From PyPI (recommended):
From source (for development or to enable native CPU optimisations):
git clone https://github.com/me60732/tulip-rs-python
cd tulip_rs_python
RUSTFLAGS="-C target-cpu=native" maturin develop --release
Requirements: Python 3.8+, Rust 1.70+
From npm (recommended):
Prebuilt binaries are provided for Linux x64, macOS x64, and macOS arm64. No Rust toolchain required.
From source (for development or native CPU optimisations):
Requirements: Node.js 18+, Rust nightly (only needed when building from source)
Feature Flags¶
| Feature | Default | Description |
|---|---|---|
simd_assets |
✅ on | Compiles indicator_by_assets::<N> for every indicator |
simd_options |
✅ on | Compiles indicator_by_options::<N> for every indicator |
Nightly toolchain
The nightly toolchain version is pinned automatically by rust-toolchain.toml in the repository root. You do not need to run rustup override set nightly manually — Cargo will select the correct toolchain when you build inside the workspace.
portable_simd is always required
portable_simd is a nightly language feature used throughout the core indicator engine — including scalar indicators — and cannot be disabled. A nightly toolchain is therefore always required regardless of which Cargo features are enabled.
To disable the SIMD multi-asset and multi-option variants (e.g. to reduce compile time):
Calling Convention¶
Every indicator in TulipRS follows the same universal signature. Once you understand it for one indicator you understand it for all of them.
indicator(
inputs: &[&[f64]], // one slice per input series
options: &[f64], // indicator parameters
optional_outputs: Option<&[bool]>, // which optional outputs to compute (or None)
) -> Result<(Vec<Vec<f64>>, IndicatorState), IndicatorError>
inputs— a slice of data slices. Single-input indicators take&[close.as_slice()]; multi-input indicators take e.g.&[high.as_slice(), low.as_slice(), close.as_slice()].options— indicator parameters asf64, in the order documented for each indicator.optional_outputs— passNoneunless you specifically want to suppress optional output series.- The return value is a tuple of
(outputs, state):outputsis aVec<Vec<f64>>— one innerVecper output series, already trimmed to the valid output length.stateis anIndicatorStatethat can be used to continue computation on new bars without reprocessing history.
inputs— a list of NumPyfloat64arrays, one per input series.options— a list offloatvalues, in the order documented for each indicator.outputs— a list of NumPy arrays, one per output series, already trimmed to valid length.state— anIndicatorStateobject that exposesbatch_indicator()and JSON serialisation.
Candlestick patterns use plain Python lists, not NumPy arrays.
See the Candlestick Patterns page for details.
inputs— an array ofnumber[], one per input series.options— an array ofnumbervalues, in the order documented for each indicator.outputs— an array ofnumber[], one per output series, already trimmed to valid length.state— a state object that exposesbatchIndicator()and JSON/Buffer serialisation.
Candlestick patterns use separate arrays per OHLC series.
See the Candlestick Patterns page for details.
Examples¶
SMA — 1 input, 1 option, 1 output¶
MACD — 1 input, 3 options, 3 outputs¶
use tulip_rs::indicators::macd::indicator;
// options: [fast_period, slow_period, signal_period]
let (outputs, state) = indicator(&[close.as_slice()], &[12.0, 26.0, 9.0], None).unwrap();
let macd_line = &outputs[0]; // MACD line
let signal = &outputs[1]; // Signal line
let histogram = &outputs[2]; // Histogram
ADX — 3 inputs, 1 option, 1 output¶
use tulip_rs::indicators::adx::indicator;
let high = vec![/* ... */];
let low = vec![/* ... */];
let close = vec![/* ... */];
let inputs = [high.as_slice(), low.as_slice(), close.as_slice()];
let (outputs, state) = indicator(&inputs, &[14.0], None).unwrap();
println!("{:?}", outputs[0]); // ADX values
import * as ti from 'tulip-rs-node';
const high = [82.15, 81.89, 83.03, 83.30, 83.85, 83.90, 83.33, 84.30, 84.84, 85.00, 85.90, 86.58, 86.98, 88.00, 87.87];
const low = [81.29, 80.64, 81.31, 82.65, 83.07, 83.11, 82.49, 82.30, 84.15, 84.11, 84.03, 85.39, 85.76, 87.17, 87.01];
const close = [81.59, 81.06, 82.87, 83.00, 83.61, 83.15, 82.84, 83.99, 84.55, 84.36, 85.53, 86.54, 86.89, 87.77, 87.29];
const [outputs, state] = ti.adx.indicator([high, low, close], [14]);
console.log(outputs[0]); // ADX values
Error Handling¶
indicator() returns a Result. The IndicatorError enum covers the common failure cases:
| Variant | Cause |
|---|---|
IndicatorError::NotEnoughData |
Input length is shorter than the indicator's minimum lookback |
IndicatorError::InvalidOption |
An option value is out of range (e.g. period < 1) |
IndicatorError::InputLengthMismatch |
Multi-input indicators received slices of different lengths |
On failure, the Python bindings raise a ValueError with a descriptive message:
Next Steps¶
| Topic | Page |
|---|---|
| Full indicator reference | Indicators — Overview |
| Indicator metadata, optional outputs, min data | Indicator API |
| SIMD acceleration concepts | SIMD |
| Streaming / incremental computation | State Management |
| Language bindings details | Language Bindings |
| Candlestick patterns | Candlestick Patterns |