fin-primitives
A zero-panic, decimal-precise foundation for high-frequency trading and quantitative
systems in Rust. fin-primitives provides the building blocks — validated types,
order book, OHLCV aggregation, streaming technical indicators, position ledger, and
composable risk monitoring — so that upstream crates and applications can focus on
strategy rather than infrastructure.
What Is Included
| Module | What it provides | Key guarantee |
|---|---|---|
[types] |
Price, Quantity, Symbol, NanoTimestamp, Side newtypes |
Validation at construction; no invalid value can exist at runtime |
[tick] |
Tick, TickFilter, TickReplayer |
Filter is pure; replayer always yields ticks in ascending timestamp order |
[orderbook] |
L2 OrderBook with apply_delta, spread, mid-price, VWAP, top-N levels |
Sequence validation; inverted spreads are detected and rolled back |
[ohlcv] |
OhlcvBar, Timeframe, OhlcvAggregator, OhlcvSeries |
Bar invariants (high >= low, etc.) enforced on every push |
[signals] |
Signal trait, SignalPipeline, Sma, Ema, Rsi |
Returns Unavailable until warm-up period is satisfied; no silent NaN |
[position] |
Position, Fill, PositionLedger |
VWAP average cost; realized and unrealized P&L net of commissions |
[risk] |
DrawdownTracker, RiskRule trait, MaxDrawdownRule, MinEquityRule, RiskMonitor |
All breaches returned as a typed Vec<RiskBreach>; never silently swallowed |
Design Principles
- Zero panics. Every fallible operation returns
Result<_, FinError>. Nounwraporexpectin production code paths. - Decimal precision. All prices and quantities use [
rust_decimal::Decimal]. Floating-point drift is structurally impossible. - Nanosecond timestamps.
NanoTimestampis a newtype overi64nanoseconds since Unix epoch, suitable for microsecond-accurate event ordering and replay. - Composable by design.
RiskRule,Signal, andTickFilterare traits; plug in your own implementations without forking. - Separation of concerns. Each module has a documented responsibility contract and an explicit "NOT Responsible For" section.
Mathematical Definitions
Price and Quantity Types
| Type | Invariant | Backing type |
|---|---|---|
Price |
d > 0 (strictly positive) |
rust_decimal::Decimal |
Quantity |
d >= 0 (non-negative) |
rust_decimal::Decimal |
NanoTimestamp |
any i64; nanoseconds since Unix epoch (UTC) |
i64 |
Symbol |
non-empty, no whitespace | String |
Technical Indicators
| Indicator | Formula | Warm-up bars | Notes |
|---|---|---|---|
| SMA(n) | Σ close[i] / n over last n bars |
n | Rolling VecDeque capped at n. Exactly equal to the arithmetic mean. |
| EMA(n) | close × k + prev_EMA × (1 − k), k = 2 / (n + 1) |
n | First n bars produce an SMA seed; subsequent bars apply the multiplier. Matches standard EMA convention (TradingView, Bloomberg). |
| RSI(n) | 100 − 100 / (1 + RS), RS = avg_gain / avg_loss using Wilder smoothing: avg = (prev_avg × (n−1) + new) / n |
n + 1 | One extra bar is required to compute the first price change. All-gain → 100; all-loss → 0; always clamped to [0, 100]. Matches Wilder (1978), TradingView, and Bloomberg. |
OHLCV Invariants
Every OhlcvBar that enters an OhlcvSeries (via push) or that is returned by
OhlcvAggregator::push_tick has been validated to satisfy:
high >= open
high >= close
low <= open
low <= close
high >= low
Any bar that violates these relationships is rejected with FinError::BarInvariant.
Order Book Guarantees
- Bids are maintained in descending price order (best bid = highest price).
- Asks are maintained in ascending price order (best ask = lowest price).
- Sequence numbers are strictly monotone;
delta.sequencemust equalbook.sequence() + 1. - A delta that would produce
best_bid >= best_askis rejected and the book is rolled back atomically.
Risk Metrics
- Drawdown %:
(peak_equity − current_equity) / peak_equity × 100. Always ≥ 0. MaxDrawdownRuletriggers whendrawdown_pct > threshold_pct(strictly greater).MinEquityRuletriggers whenequity < floor(strictly less).
Position P&L
Average-cost (FIFO) method:
- Realized P&L (on reduce/close):
closed_qty × (fill_price − avg_cost)for long positions. - Unrealized P&L:
position_qty × (current_price − avg_cost). - Both are net of commissions.
Quickstart
Add to Cargo.toml:
[]
= "1.1"
= "1"
Example: Buy, mark-to-market, check risk
use ;
use ;
use ;
use dec;
use HashMap;
Example: Tick-to-OHLCV with SMA signal
use ;
use SignalPipeline;
use Sma;
use Tick;
use ;
use dec;
Example: Order book with VWAP fill
use ;
use ;
use dec;
Example: RSI(14) computation
use Rsi;
use ;
use OhlcvBar;
use ;
use dec;
API Reference
types module
// Validated newtypes — construction is the only fallible step.
new // d > 0
new // d >= 0
zero // convenience
new // non-empty, no whitespace
now // current UTC nanoseconds
to_datetime
tick module
new .notional // price * quantity
new // matches everything
.symbol // restrict to symbol
.side // restrict to side
.min_quantity // restrict to qty >= min
.matches // sorts ascending by timestamp
.next_tick .remaining .reset
orderbook module
new .apply_delta // SequenceMismatch | InvertedSpread
.best_bid .best_ask .spread // best_ask - best_bid
.mid_price // (bid + ask) / 2
.vwap_for_qty // InsufficientLiquidity
.top_bids // descending
.top_asks // ascending
.sequence .bid_count / ask_count
ohlcv module
validate // BarInvariant
typical_price // (H + L + C) / 3
range // H - L
is_bullish // close >= open
Seconds | Minutes | Hours | Days
to_nanos .push_tick .flush .current_bar .push .window
.closes .volumes
signals module
// Signal trait — implement for custom indicators
new // period bars warm-up
new // period bars warm-up; SMA seed
new // period + 1 bars warm-up; Wilder smoothing
new
.add // builder pattern
.update .ready_count // SignalValue: Scalar(Decimal) | Unavailable
position module
new
.apply_fill // realized P&L
.unrealized_pnl .market_value .is_flat .apply_fill // InsufficientFunds
.position .cash .realized_pnl_total .unrealized_pnl_total .equity
risk module
new
.update
.current_drawdown_pct // (peak - current) / peak * 100, always >= 0
.peak .is_below_threshold // Implement RiskRule for custom rules
MaxDrawdownRule // fires when dd > threshold
MinEquityRule // fires when equity < floor
new
.add_rule // builder pattern
.update // empty if compliant
Precision and Accuracy Notes
Decimal arithmetic
All prices and quantities use [rust_decimal::Decimal] (128-bit fixed-point).
This eliminates all floating-point drift:
// This is safe and exact with Decimal — never silently rounds:
let price = new.unwrap;
let qty = new.unwrap;
let notional = price.value * qty.value; // exactly 150250.00
Indicator precision
- SMA: exact arithmetic;
sum / nviachecked_div. Overflow returnsFinError::ArithmeticOverflow. - EMA: multiplier
k = 2 / (n + 1)is computed in Decimal. Small rounding error accumulates over very long series but is bounded byDecimal's 28-digit precision. - RSI: Wilder smoothing carries the same Decimal precision. Edge cases:
- All-gains (avg_loss = 0): returns exactly 100.
- All-losses (avg_gain = 0): returns exactly 0.
- Always clamped to [0, 100].
Order book VWAP
vwap_for_qty sweeps levels from best to worst with exact Decimal arithmetic.
Result is total_cost / total_qty where both accumulators are Decimal — no
intermediate f64 conversion.
Performance Notes
- O(1) order book mutations:
apply_deltaperforms a singleBTreeMap::insertorBTreeMap::remove. The inverted-spread check reads two keys and does not allocate. - O(1) streaming indicators:
EmaandRsimaintain a constant-size state regardless of history length.Smauses aVecDequecapped atperiodelements. - Zero-copy tick replay:
TickReplayersorts once at construction and returns shared references on eachnext_tickcall; no per-tick heap allocation. - Composable risk without boxing overhead:
RiskMonitor::updateis a linear scan overVec<Box<dyn RiskRule>>; one virtual dispatch per rule per equity update.
Architecture Overview
Tick stream
|
TickReplayer / TickFilter
|
+-----------+-----------+
| |
OhlcvAggregator OrderBook
| (apply_delta)
OhlcvSeries |
| vwap_for_qty / spread
SignalPipeline
(Sma / Ema / Rsi)
|
SignalMap
|
PositionLedger (Fill)
|
DrawdownTracker
|
RiskMonitor
|
Vec<RiskBreach>
All arrows represent pure data flow. No shared mutable state crosses module
boundaries. Wrap any component in Arc<Mutex<_>> for multi-threaded use.
Running Tests
# Unit and integration tests
# With proptest cases increased (recommended for CI)
PROPTEST_CASES=1000
# Release-mode correctness check
# Check lints
# Build docs locally
# Security audit
The test suite includes unit tests in every module, integration tests in tests/,
and property-based tests using proptest.
Custom Implementations
Custom RiskRule
use ;
use Decimal;
Custom Signal
use ;
use OhlcvBar;
use FinError;
Contributing
- Fork the repository and create a branch from
main. - All public items must have
///doc comments explaining purpose, arguments, return values, and errors. - All fallible operations must return
Result; nounwrap,expect, orpanic!in non-test code. - Every new behavior must have at least one test covering the happy path and one covering the error/edge case.
- Run
cargo fmt,cargo clippy -- -D warnings, andcargo testbefore opening a pull request. - Update
CHANGELOG.mdunder[Unreleased]with a brief description of your change.
License
MIT — see LICENSE.
Also used inside tokio-prompt-orchestrator, a production Rust orchestration layer for LLM pipelines.