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 ofFloat64Array, one per input series.options— an array ofnumbervalues, in the order documented for each indicator.outputs— an array ofFloat64Array, 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
import * as ti from 'tulip-rs-node';
const close = Float64Array.from([81.59, 81.06, 82.87, 83.00, 83.61,
83.15, 82.84, 83.99, 84.55, 84.36]);
// options: [fast_period, slow_period, signal_period]
const [outputs, state] = ti.macd.indicator([close], [12, 26, 9]);
const macdLine = outputs[0]; // MACD line
const signal = outputs[1]; // Signal line
const 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 = Float64Array.from([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 = Float64Array.from([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 = Float64Array.from([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 |