# rsta
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.
[](https://github.com/lsh0x/rsta/actions)
[](https://codecov.io/gh/lsh0x/rsta)
[](https://crates.io/crates/rsta)
[](https://docs.rs/rsta)
[](LICENSE)
## At a glance
```rust
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`.
## What's in the box
### 26 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 layer
`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
from `Donchian`)
- `Divergence` — bullish/bearish divergences between price and an oscillator
- `SignalExt::and` / `or` / `not` combinators for composing signals
### Backtesting engine
`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.
### CSV import/export *(opt-in via the `csv` feature)*
```toml
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.
## Installation
```toml
[dependencies]
rsta = "0.1"
```
Optional: enable the CSV pipeline.
```toml
[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.
## Verifying correctness
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:
```bash
pip install pandas pandas-ta
python scripts/gen_golden.py
```
This 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.
## Examples
```bash
# 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.csv
```
## Benchmarks
Microbenchmarks via [criterion]:
```bash
cargo bench --bench indicators # 11 indicators × 100k synthetic bars
cargo bench --bench backtest # SMA crossover on BTC daily + 1M-bar synthetic
```
On a 2024 M-class laptop (release, single thread):
| `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.
## Comparison
| 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](https://github.com/Lsh0x/rsta/issues/26)) | no | n/a |
[`ta-rs`]: https://github.com/greyblake/ta-rs
[`pandas-ta`]: https://github.com/twopirllc/pandas-ta
[criterion]: https://github.com/bheisler/criterion.rs
[pandas-ta]: https://github.com/twopirllc/pandas-ta
## Stability
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` / `Signal` traits 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](https://github.com/Lsh0x/rsta/issues/26).
## Contributing
PRs welcome. New indicators should:
1. Live in their own file under `src/indicators/<family>/<name>.rs`.
2. Implement `Indicator<T, O>` for whichever input type makes sense
(`Indicator<f64, f64>` for close-priced, `Indicator<Candle, _>` for
OHLCV-based).
3. Provide a `reset_state()` inherent method that the trait `reset()`
delegates to.
4. Override `name()` and `period()` on the trait when applicable.
5. Include unit tests covering construction validation, warmup, and
batch-vs-streaming parity.
6. If practical, add a pandas-ta golden in `scripts/gen_golden.py` and
a comparison test in `tests/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.
## License
MIT. See [LICENSE](LICENSE).