fin-primitives
A Rust library of the basic parts every trading program needs: exact prices that refuse bad values, an order book, candles built from trades, 700+ indicators, option pricing and risk limits.
For Rust developers writing trading bots, backtesters, market-data tools or quant research code who do not want to rebuild (and re-debug) the same pieces.
Install
It is a library, so there is nothing to download or install system-wide (Rust 1.81 or newer).
rust_decimal_macros gives you the dec!(64250.50) literal used in every example. Or in
Cargo.toml: fin-primitives = "2.14". Want to see it first? git clone https://gitlab.com/mattbusel/fin-primitives && cd fin-primitives && cargo run --example order_book.
How it works
Trades come in as Ticks. OhlcvAggregator::push_tick drops each one into a time bucket and
hands back the finished candle the moment a trade for the next bucket arrives. Candles feed
indicators, which say Unavailable until they have enough history instead of making up a number.
Money works the same way: fills and price marks go into a PositionLedger, its account value
goes into a RiskMonitor, and every rule that is broken comes back as a RiskBreach you can act on.
Examples
Four programs ship in examples/. No network, no API keys, same output every run.
| Run this | You get |
|---|---|
cargo run --example order_book |
a BTC-USD depth ladder, spread, micro-price, the cost of a 5 BTC market buy, and two bad updates rejected |
cargo run --example candles |
337 trades rolled into 32 one-minute candles, drawn with EMA(9) and RSI(14) |
cargo run --example position_risk |
a trading session marked to market, with a drawdown rule and an equity floor firing |
cargo run --example option_chain |
a Black-Scholes option chain with Greeks and an implied-volatility round trip |
order_book: the book knows its spread and middle price, and refuses updates that would cross it or skip a sequence number.
position_risk (real output, run 2026-09-28, NO_COLOR=1):
# event equity drawdown risk
5 sell 100 AAPL @ 179.10 101,237.00 0.00% ok
6 mark MSFT 398.10 99,865.00 1.36% ok
7 mark AAPL 166.80 98,635.00 2.57% ok
8 mark MSFT 371.50 96,507.00 4.67% BREACH max_drawdown, min_equity
max_drawdown: drawdown 4.67% > 4.00%
min_equity: equity 96507.00 < floor 96600
rejected buy 1000 AAPL Insufficient funds: need 171351.00, have 79866.00
(rows 1 to 4 and 9 to 10 cut for length; the diagram above plays the whole session.)
Use it in 3 steps
1. Make a project and add the crate
&&
2. Put this in src/main.rs
use ;
use ;
use dec;
3. Run it
You will see:
Price::new(-5) -> Price must be positive, got -5
spread 0.50
mid price 64250.25
buy 1 BTC at 64250.58 average
A negative price never gets into your program, the book knows its own spread and middle price, and "what would buying 1 BTC cost" is one call (0.842 BTC fill at the best ask, the other 0.158 at the next level up). This exact program was built against fin-primitives 2.14 from crates.io and run on 2026-09-28.
Compared with TA-Lib
Same 100,000 seeded OHLCV bars through both libraries (method, script and full tables;
i7-13700KF, TA-Lib 0.8.1). Where the definitions match, the values agree to 2e-11 or
better; fin-primitives works in exact decimals, so it is the float64 side that rounds.
Two definitions differ on purpose: Atr is a simple average of true range (it equals
TA-Lib's SMA(TRANGE), not Wilder's ATR), and Obv starts at 0 rather than at the first
bar's volume. TA-Lib is much faster: its batch functions take 0.2 to 12 ns per bar, about
100 times less than fin-primitives' exact-decimal update(). Use TA-Lib to crunch long
float64 histories; use fin-primitives for exact values, bar-by-bar state and no C dependency.
| indicator | max difference vs TA-Lib (after bar 1,000) | fin-primitives, ns per bar | TA-Lib batch, ns per bar |
|---|---|---|---|
| SMA(20) / EMA(20) | 9.8e-13 / 5.7e-14 | 74 / 149 | 0.8 / 1.3 |
| RSI(14) | 1.8e-13 | 252 | 2.1 |
| MACD(12,26,9) histogram | 5.0e-14 (seeded differently: 0.036 in the first bars) | 362 | 1.7 |
| Bollinger(20, 2) bands | 1.6e-11 | 1023 | 2.7 |
| ATR(14) | equals TA-Lib SMA(TRANGE, 14) to 1.2e-14 |
86 | 0.8 |
| Stochastic fast %K / %D (14, 3) | 6.7e-13 / 1.3e-12 | 174 / 259 | 7.7 |
| Williams %R(14) / CCI(20) / MFI(14) | 6.7e-13 / 1.0e-11 / 1.3e-12 | 170 / 1060 / 589 | 1.9 / 12.2 / 5.9 |
| ADX(14) | 3.7e-13, except near exact up/down ties that float64 splits | 794 | 6.2 |
| OBV | equals TA-Lib OBV - volume[0] exactly |
13 | 2.8 |
Documentation
| Read this | For |
|---|---|
| docs.rs/fin-primitives | every type and method, with examples that compile |
| docs/ARCHITECTURE.md | how the modules connect, what each one guarantees, design rules, writing your own indicator or risk rule |
| docs/REFERENCE.md | module guides (indicators, series analytics, ledger, drawdown, attribution, Greeks, regimes, backtester, Monte Carlo, factor models, yield curves and more), math definitions, API listing |
| docs/TESTING.md | running the tests and benchmarks, current test status |
| CHANGELOG.md | what changed in each version |
| Project site | the same overview as a web page |
Optional Python bindings are behind the python feature (maturin develop --features python).
Research and engineering library. It does not place orders, and nothing here is financial advice.
Contributing
Issues and pull requests are welcome. Public items need /// docs, fallible code returns
Result (no unwrap, expect or panic! outside tests), and new behavior needs a test.
Run cargo fmt, cargo clippy and cargo test --doc before opening a PR. See
CONTRIBUTING.md.
License and related projects
MIT, see LICENSE. fin-stream builds on this crate: it turns live Binance, Coinbase, Alpaca and Polygon trade messages into ticks, bars and order books.