rsta 0.1.0

Technical analysis indicators, streaming signals, and a single-asset backtesting engine for Rust
Documentation
# 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.

[![CI](https://github.com/lsh0x/rsta/workflows/CI/badge.svg)](https://github.com/lsh0x/rsta/actions)
[![Codecov](https://codecov.io/gh/lsh0x/rsta/branch/main/graph/badge.svg)](https://codecov.io/gh/lsh0x/rsta)
[![Crates.io](https://img.shields.io/crates/v/rsta.svg)](https://crates.io/crates/rsta)
[![Docs](https://docs.rs/rsta/badge.svg)](https://docs.rs/rsta)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](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

| 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 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):

| 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.

## Comparison

|  | 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]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).