Rust Statistical Technical Analysis — a focused, well-tested toolkit for trend, momentum, volume, and volatility indicators, plus a streaming signals layer and a single-asset backtesting engine.
use rsta::backtest::{Action, BacktestConfig, Backtester, Context, Quantity, Strategy};
use rsta::indicators::trend::Sma;
use rsta::indicators::{Candle, Indicator};
use rsta::signals::{CrossDown, CrossUp, Signal, SignalEvent};
struct Crossover { fast: Sma, slow: Sma, up: CrossUp, down: CrossDown }
impl Strategy for Crossover {
fn on_candle(&mut self, c: &Candle, _ctx: &Context) -> Action {
let f = <Sma as Indicator<f64, f64>>::next(&mut self.fast, c.close).unwrap();
let s = <Sma as Indicator<f64, f64>>::next(&mut self.slow, c.close).unwrap();
let (Some(f), Some(s)) = (f, s) else { return Action::Hold };
match (self.up.next((f, s)), self.down.next((f, s))) {
(Some(SignalEvent::Long), _) => Action::EnterLong(Quantity::AllCash),
(_, Some(SignalEvent::Short)) => Action::Exit,
_ => Action::Hold,
}
}
}
let candles: Vec<Candle> = /* … */ vec![];
let bt = Backtester::new(BacktestConfig::default());
let mut strat = Crossover {
fast: Sma::new(20).unwrap(),
slow: Sma::new(50).unwrap(),
up: CrossUp::new(),
down: CrossDown::new(),
};
let result = bt.run(&candles, &mut strat);
println!("return = {:.2}%", result.metrics.total_return * 100.0);A runnable version of this on 12 years of real Kraken BTC/USD daily data
ships as cargo run --release --example sma_crossover_backtest.
| Family | Indicators |
|---|---|
| Trend | Sma, Ema, Wma, Dema, Tema, Hma, Macd (+MacdResult), Adx (+AdxResult), Sar, Ichimoku (+IchimokuResult), pivot_classic/pivot_fibonacci/pivot_camarilla (+PivotResult) |
| Momentum | Rsi, StochasticOscillator (+StochasticResult), WilliamsR, Cci |
| Volatility | Atr, BollingerBands (+BollingerBandsResult), KeltnerChannels (+KeltnerChannelsResult), Std, Donchian (+DonchianResult) |
| Volume | Obv, Vroc, Adl, Cmf, Mfi, Vwap |
| Transforms | heikin_ashi(&[Candle]) -> Vec<Candle> |
Every indicator implements the Indicator<T, O> trait with both
calculate(&[T]) (batch) and next(T) (streaming) — the two paths
produce identical values bar-for-bar. Close-priced indicators also
accept &[Candle] directly; multi-output indicators emit a typed
struct so consumers don't need to remember column orders.
signals::{Signal, SignalEvent} turns indicator outputs into discrete
trading events:
CrossUp/CrossDown— two-series crossovers (fast MA vs slow MA, …)ThresholdAbove/ThresholdBelow— value crossing a fixed level (RSI breaching 70 / 30, …)Breakout— value escaping a(value, upper, lower)channel (drive fromDonchian)Divergence— bullish/bearish divergences between price and an oscillatorSignalExt::and/or/notcombinators for composing signals
backtest::Backtester runs a Strategy against a &[Candle] slice
with configurable proportional fees and slippage. Single asset, single
position; long, short, or flat. Tracks cash, position MTM, full trade
log, per-bar equity curve. Reports total return, max drawdown,
annualised Sharpe, win rate, and profit factor.
rsta = { version = "0.1", features = ["csv"] }csv::CsvFormatter loads OHLCV from a CSV, runs an arbitrary set of
registered indicators, and writes an enriched CSV with one column per
indicator.
[dependencies]
rsta = "0.1"Optional: enable the CSV pipeline.
[dependencies]
rsta = { version = "0.1", features = ["csv"] }MSRV is 1.82 (std::iter::repeat_n). The crate compiles cleanly
under stable, beta, and nightly on Linux, macOS, and Windows.
The crate ships golden CSVs generated by pandas-ta against 12 years
of real Kraken XBTUSD daily OHLCV (tests/data/btc_usd_daily.csv,
~4.5k bars from 2013-10-06 to 2026-04-21). The integration tests in
tests/golden_indicators.rs compare rsta's outputs against this
reference at tight tolerance (1e-6 for SMA/EMA/ATR/MACD, 1e-2 for
RSI past warmup, where Wilder seed conventions diverge). To regenerate:
pip install pandas pandas-ta
python scripts/gen_golden.pyThis is the layer that catches subtle bugs cross-implementation —
during 0.0.3 it surfaced a real internal inconsistency in Ema that
synthetic tests had missed.
# End-to-end: indicators → signals → backtest → metrics on real BTC daily
cargo run --release --example sma_crossover_backtest
# Streaming indicator usage with RSI threshold signals
cargo run --release --example realtime_streaming
# Enrich a CSV with indicator columns (csv feature required)
cargo run --release --features csv --example csv_to_indicators -- input.csv output.csvMicrobenchmarks via criterion:
cargo bench --bench indicators # 11 indicators × 100k synthetic bars
cargo bench --bench backtest # SMA crossover on BTC daily + 1M-bar syntheticOn a 2024 M-class laptop (release, single thread):
| Indicator | 100k bars | elem/s |
|---|---|---|
Sma(20) |
~144 µs | ~690 M |
Ema(20) |
~155 µs | ~640 M |
Rsi(14) |
~620 µs | ~160 M |
BollingerBands(20) |
~3.0 ms | ~33 M |
Run cargo bench locally for your hardware's numbers.
| rsta | ta-rs |
pandas-ta |
|
|---|---|---|---|
| Language | Rust | Rust | Python |
| Indicators | 26 | ~25 | 130+ |
Streaming API (next() per bar) |
yes | yes | no |
| Signals layer (Cross / Threshold / Breakout / Divergence + combinators) | yes | no | partial |
| Backtest engine | yes (single-asset) | no | no |
| Golden tests vs pandas-ta on real data | yes | no | n/a |
| Generic over numeric type | not yet (#26) | no | n/a |
This is the first 0.1.x release. The API is usable and well-tested,
but reserves the right to evolve before 1.0:
- Indicator constructors and their result types are unlikely to move.
- The
Indicator/Signaltraits may grow optional methods (with defaults) but are expected to stay source-compatible. - Backtester semantics (single-asset, exec-at-close) are intentionally scoped — multi-asset and tick-precision will be opt-in additions.
Open issues track known direction, including generic numeric support.
PRs welcome. New indicators should:
- Live in their own file under
src/indicators/<family>/<name>.rs. - Implement
Indicator<T, O>for whichever input type makes sense (Indicator<f64, f64>for close-priced,Indicator<Candle, _>for OHLCV-based). - Provide a
reset_state()inherent method that the traitreset()delegates to. - Override
name()andperiod()on the trait when applicable. - Include unit tests covering construction validation, warmup, and batch-vs-streaming parity.
- If practical, add a pandas-ta golden in
scripts/gen_golden.pyand a comparison test intests/golden_indicators.rs.
cargo fmt, cargo clippy --all-features --all-targets -- -D warnings,
and cargo test --all-features all need to be green before review.
MIT. See LICENSE.