fin_primitives/lib.rs
1//! # fin-primitives
2//!
3//! The basic parts of trading software, done once and checked: exact price and
4//! size types that refuse bad values, an order book, candles built from trades,
5//! indicators, option prices and risk limits.
6//!
7//! 
8//!
9//! Add it (the `dec!` macro for decimal literals needs both `rust_decimal` crates):
10//!
11//! ```text
12//! cargo add fin-primitives rust_decimal rust_decimal_macros
13//! ```
14//!
15//! The main types: [`Price`](types::Price), [`Quantity`](types::Quantity) and
16//! [`Symbol`](types::Symbol) (validated values), [`OrderBook`](orderbook::OrderBook),
17//! [`OhlcvAggregator`](ohlcv::OhlcvAggregator) (ticks to candles), the
18//! [`Signal`](signals::Signal) trait and its indicators,
19//! [`PositionLedger`](position::PositionLedger), [`RiskMonitor`](risk::RiskMonitor),
20//! [`BlackScholes`](greeks::BlackScholes), and one error type, [`FinError`].
21//!
22//! ## A first look
23//!
24//! Build a book from sequenced deltas, read the top of book, and price a
25//! market order by walking the levels:
26//!
27//! ```
28//! use fin_primitives::orderbook::{BookDelta, DeltaAction, OrderBook};
29//! use fin_primitives::types::{Price, Quantity, Side, Symbol};
30//! use rust_decimal_macros::dec;
31//!
32//! # fn main() -> Result<(), fin_primitives::FinError> {
33//! let mut book = OrderBook::new(Symbol::new("BTC-USD")?);
34//! let levels = [
35//! (Side::Ask, dec!(64250.50), dec!(0.842)),
36//! (Side::Ask, dec!(64251.00), dec!(1.310)),
37//! (Side::Bid, dec!(64250.00), dec!(1.204)),
38//! (Side::Bid, dec!(64249.50), dec!(0.655)),
39//! ];
40//! for (seq, (side, price, qty)) in (1..).zip(levels) {
41//! book.apply_delta(BookDelta {
42//! side,
43//! price: Price::new(price)?,
44//! quantity: Quantity::new(qty)?,
45//! action: DeltaAction::Set,
46//! sequence: seq,
47//! })?;
48//! }
49//!
50//! assert_eq!(book.spread(), Some(dec!(0.50)));
51//! assert_eq!(book.mid_price(), Some(dec!(64250.25)));
52//!
53//! // Buying 1 BTC takes all 0.842 at the best ask and 0.158 at the next level.
54//! let vwap = book.vwap_for_qty(Side::Ask, Quantity::new(dec!(1))?)?;
55//! assert_eq!(vwap, dec!(64250.579));
56//!
57//! // A delta that would cross the book is rejected and rolled back.
58//! let crossing = BookDelta {
59//! side: Side::Bid,
60//! price: Price::new(dec!(64251.00))?,
61//! quantity: Quantity::new(dec!(2))?,
62//! action: DeltaAction::Set,
63//! sequence: 5,
64//! };
65//! assert!(book.apply_delta(crossing).is_err());
66//! assert_eq!(book.sequence(), 4);
67//! # Ok(())
68//! # }
69//! ```
70//!
71//! 
72//!
73//! Ticks become bars, and bars feed indicators. Indicators return
74//! [`SignalValue::Unavailable`](signals::SignalValue::Unavailable) until they
75//! have enough history, never a NaN or a zero:
76//!
77//! ```
78//! use fin_primitives::ohlcv::{OhlcvAggregator, Timeframe};
79//! use fin_primitives::signals::indicators::Sma;
80//! use fin_primitives::signals::{BarInput, Signal, SignalValue};
81//! use fin_primitives::tick::Tick;
82//! use fin_primitives::types::{NanoTimestamp, Price, Quantity, Side, Symbol};
83//! use rust_decimal_macros::dec;
84//!
85//! # fn main() -> Result<(), fin_primitives::FinError> {
86//! let sym = Symbol::new("ETH-USD")?;
87//! let mut agg = OhlcvAggregator::new(sym.clone(), Timeframe::Minutes(1))?;
88//! let mut sma = Sma::new("sma3", 3)?;
89//!
90//! let mut values = Vec::new();
91//! for (minute, close) in [(0, dec!(3180)), (1, dec!(3190)), (2, dec!(3200)), (3, dec!(3230))] {
92//! let tick = Tick::new(
93//! sym.clone(),
94//! Price::new(close)?,
95//! Quantity::new(dec!(1))?,
96//! Side::Bid,
97//! NanoTimestamp::from_secs(1_767_625_200 + minute * 60),
98//! );
99//! for bar in agg.push_tick(&tick)? {
100//! values.push(sma.update(&BarInput::from(&bar))?);
101//! }
102//! }
103//! // Three bars have closed; the fourth is still open.
104//! assert_eq!(values[0], SignalValue::Unavailable);
105//! assert_eq!(values[1], SignalValue::Unavailable);
106//! assert_eq!(values[2], SignalValue::Scalar(dec!(3190)));
107//! # Ok(())
108//! # }
109//! ```
110//!
111//! ## Runnable examples
112//!
113//! The repository ships examples that print formatted, colored output:
114//!
115//! | Command | Shows |
116//! |---------|-------|
117//! | `cargo run --example order_book` | depth ladder, spread, micro-price, VWAP fill, rejected deltas |
118//! | `cargo run --example candles` | ticks to 1-minute candles, EMA and RSI with warm-up |
119//! | `cargo run --example position_risk` | fills, mark-to-market, drawdown and equity-floor breaches |
120//! | `cargo run --example option_chain` | Black-Scholes chain with Greeks and an implied-vol round trip |
121//!
122//! ## Where things live
123//!
124//! | Module | What it provides |
125//! |--------|------------------|
126//! | [`types`] | [`Price`](types::Price), [`Quantity`](types::Quantity), [`Symbol`](types::Symbol), [`NanoTimestamp`](types::NanoTimestamp), [`Side`](types::Side) |
127//! | [`tick`] | [`Tick`](tick::Tick), `TickFilter`, `TickReplayer` |
128//! | [`orderbook`] | [`OrderBook`](orderbook::OrderBook): L2 book with sequence checks and crossed-book rollback |
129//! | [`ohlcv`] | [`OhlcvBar`](ohlcv::OhlcvBar), [`OhlcvAggregator`](ohlcv::OhlcvAggregator), `OhlcvSeries` analytics |
130//! | [`signals`] | the [`Signal`](signals::Signal) trait, `SignalPipeline`, and several hundred indicators in [`signals::indicators`] |
131//! | [`position`] | [`Fill`](position::Fill), [`Position`](position::Position), [`PositionLedger`](position::PositionLedger), Kelly sizing |
132//! | [`risk`] | `DrawdownTracker`, the [`RiskRule`](risk::RiskRule) trait, [`RiskMonitor`](risk::RiskMonitor), VaR and stress tools |
133//! | [`greeks`] | [`BlackScholes`](greeks::BlackScholes) pricing, Greeks, implied volatility, multi-leg spreads |
134//! | [`backtest`] | bar-by-bar `Backtester`, `Strategy` trait, `WalkForwardOptimizer` |
135//! | [`async_signals`] | Tokio-based `StreamingSignalPipeline` |
136//! | [`regime`] | Hurst exponent, GARCH(1,1), correlation-breakdown regime detection |
137//!
138//! Further modules cover portfolio optimization, factor models, yield curves,
139//! fixed income, credit, derivatives, execution cost, microstructure, Monte
140//! Carlo, pairs trading, tax lots and more; see the module list below.
141//!
142//! ## Feature flags
143//!
144//! | feature | default | adds |
145//! |---|---|---|
146//! | `serde` | yes | `Serialize`/`Deserialize` on the data types; deserializing re-runs validation |
147//! | `async` | yes | `async_signals`: a Tokio task that runs a signal pipeline over a channel |
148//! | `ta`, `yata`, `wickra` | no | [`interop`] with those crates: their indicators read fin-primitives bars, their bars convert to [`BarInput`](signals::BarInput) |
149//! | `arrow` | no | `arrow`: bars to and from Apache Arrow with exact `Decimal128` columns |
150//! | `python` | no | PyO3 bindings, built with maturin from the repository's `python/` folder |
151//!
152//! [`normal`] has the standard normal CDF, PDF and inverse used by every pricer and
153//! VaR model here (accurate to about 1e-15 relative, including the tails).
154//!
155//! ## Design
156//!
157//! - **Validated at construction.** `Price::new` rejects zero and negative
158//! values; `Quantity::new` rejects negatives; `Symbol::new` rejects empty or
159//! whitespace strings. Code that holds one of these types can trust it.
160//! - **Decimal where money is.** Prices, quantities, P&L and order-book math
161//! use [`rust_decimal::Decimal`]. Statistical models (GARCH, optimizers,
162//! Black-Scholes internals) compute in `f64` and convert at the boundary.
163//! - **Typed errors.** Fallible operations return `Result<_, FinError>`, and
164//! the crate's Clippy config warns on `unwrap`, `expect` and `panic`.
165//! - **Traits at the seams.** [`risk::RiskRule`], [`signals::Signal`] and
166//! [`tick::TickFilter`] are traits, so your own rules and indicators plug in
167//! next to the built-in ones.
168//! - **No unsafe code.** The crate is `#![forbid(unsafe_code)]`.
169//!
170//! Sister crate: [fin-stream](https://gitlab.com/mattbusel/fin-stream) handles
171//! real-time market data ingestion on top of these types.
172
173#![forbid(unsafe_code)]
174#![deny(missing_docs)]
175#![cfg_attr(docsrs, feature(doc_cfg))]
176
177#[cfg(feature = "async")]
178#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
179pub mod async_signals;
180pub mod backtest;
181pub mod error;
182pub mod greeks;
183pub mod normal;
184pub mod interop;
185#[cfg(feature = "arrow")]
186#[cfg_attr(docsrs, doc(cfg(feature = "arrow")))]
187pub mod arrow;
188pub mod ohlcv;
189pub mod orderbook;
190pub mod position;
191pub mod risk;
192pub mod signals;
193pub mod tick;
194pub mod types;
195pub mod pnl;
196pub mod correlation;
197pub mod latency;
198pub mod scenario;
199pub mod microstructure;
200pub mod ml;
201pub mod regime;
202pub mod cross_asset;
203pub mod attribution;
204pub mod options;
205pub mod volatility;
206pub mod impact;
207pub mod portfolio;
208
209#[cfg(feature = "python")]
210pub mod python;
211pub mod factor;
212pub mod execution;
213pub mod montecarlo;
214pub mod yield_curve;
215pub mod events;
216pub mod crypto;
217pub mod derivatives;
218pub mod technical;
219pub mod fixed_income;
220pub mod liquidity;
221pub mod pairs_trading;
222pub mod ml_features;
223pub mod performance;
224pub mod clustering;
225pub mod tax;
226pub mod rebalancing;
227pub mod funding;
228pub mod alternative_data;
229pub mod arbitrage;
230pub mod execution_cost;
231pub mod credit;
232
233pub use error::FinError;
234
235/// Compiles and runs the Rust code blocks in README.md as doc tests, so the README
236/// cannot drift from the API.
237#[cfg(doctest)]
238#[doc = include_str!("../README.md")]
239pub struct ReadmeDoctests;