fin-primitives 2.14.3

Checked building blocks for Rust trading code: exact decimal price and quantity types, a level-2 order book, ticks to OHLCV candles, 700+ streaming indicators, Black-Scholes Greeks, a position ledger and risk limits.
Documentation

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

cargo add fin-primitives rust_decimal rust_decimal_macros

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

cargo new book-demo && cd book-demo

cargo add fin-primitives rust_decimal rust_decimal_macros

2. Put this in src/main.rs

use fin_primitives::orderbook::{BookDelta, DeltaAction, OrderBook};
use fin_primitives::types::{Price, Quantity, Side, Symbol};
use rust_decimal_macros::dec;

fn main() -> Result<(), fin_primitives::FinError> {
    // 1. Values are checked when you create them.
    println!("Price::new(-5) -> {}", Price::new(dec!(-5)).unwrap_err());

    // 2. Build a small BTC-USD order book from four quotes.
    let mut book = OrderBook::new(Symbol::new("BTC-USD")?);
    let quotes = [
        (Side::Ask, dec!(64250.50), dec!(0.842)),
        (Side::Ask, dec!(64251.00), dec!(1.310)),
        (Side::Bid, dec!(64250.00), dec!(1.204)),
        (Side::Bid, dec!(64249.50), dec!(0.655)),
    ];
    for (seq, (side, price, qty)) in (1..).zip(quotes) {
        book.apply_delta(BookDelta {
            side,
            price: Price::new(price)?,
            quantity: Quantity::new(qty)?,
            action: DeltaAction::Set,
            sequence: seq,
        })?;
    }

    // 3. Ask it questions.
    println!("spread         {}", book.spread().unwrap_or_default());
    println!("mid price      {}", book.mid_price().unwrap_or_default());
    let one_btc = Quantity::new(dec!(1))?;
    println!("buy 1 BTC at   {} average", book.vwap_for_qty(Side::Ask, one_btc)?.round_dp(2));
    Ok(())
}

3. Run it

cargo run

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.