feat: order-book microstructure indicators (part 1 of 4) (#112)
* feat(core): add microstructure input types (OrderBook, Trade, TradeQuote) New non-OHLCV value types for the order-book / trade-flow indicator family: Level, OrderBook (sorted, uncrossed depth snapshot), Side, Trade (with aggressor side), and TradeQuote (trade paired with prevailing mid). Each has a validating constructor plus a new_unchecked hot-path constructor, with full unit coverage. Adds InvalidOrderBook / InvalidTrade error variants. * feat(core): add 5 order-book microstructure indicators OrderBookImbalanceTop1/TopN/Full (signed depth imbalance), Microprice (size-weighted fair value), and QuotedSpread (top-of-book spread in bps). All consume the OrderBook snapshot type, emit f64, are stateless and ready after the first snapshot, with full unit coverage. Registers a new Microstructure family in the taxonomy. * feat(bindings): expose order-book microstructure indicators Python, Node, and WASM bindings for OrderBookImbalanceTop1/TopN/Full, Microprice and QuotedSpread. Each takes a depth snapshot via four equal-length (bid_px, bid_sz, ask_px, ask_sz) arrays. Python and Node expose a batch over a list of snapshots; WASM exposes per-snapshot update (the streaming model that fits a browser book feed). Regenerates node index.d.ts/.js and registers the new InvalidOrderBook/InvalidTrade arms in the Python error mapping. * test(bindings,fuzz): cover order-book microstructure indicators Python: smoke, reference values, streaming-vs-batch, lifecycle/repr and input validation (mismatched lengths, crossed book, misordered levels, zero levels) for all five order-book indicators. Node: reference values, streaming-vs-batch, and rejection cases. Adds an indicator_update_orderbook fuzz target driving every order-book indicator over arbitrary (incl. degenerate) snapshots. * bench(microstructure): synthetic order-book benchmarks Add a bench_orderbook_input harness and synthesise a five-level book around each candle close (no order-book dataset ships with the repo). Benches the cheapest (top-of-book imbalance) and most-expensive (full-depth imbalance) plus microprice, matching the curated cheapest/expensive-per-family approach. * docs: add Microstructure family + bump indicator counter to 224 README gains the Microstructure family row (order-book imbalance, microprice, quoted spread) and the indicator counter goes 219 -> 224 across seventeen families; CHANGELOG records the new order-book indicators and value types.
This commit is contained in:
@@ -52,6 +52,13 @@ test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "indicator_update_orderbook"
|
||||
path = "fuzz_targets/indicator_update_orderbook.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "tick_aggregator"
|
||||
path = "fuzz_targets/tick_aggregator.rs"
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#![no_main]
|
||||
//! Fuzz order-book `Indicator<Input = OrderBook>` implementations with
|
||||
//! arbitrary depth snapshots.
|
||||
//!
|
||||
//! Each iteration consumes a byte stream, interprets it as a sequence of
|
||||
//! `f64` values (8 bytes each), packs consecutive values into `(price, size)`
|
||||
//! levels, and groups levels into order-book snapshots. Books are built with
|
||||
//! `OrderBook::new_unchecked` so the fuzzer can explore degenerate shapes
|
||||
//! (empty sides, crossed books, non-finite prices, negative sizes) that the
|
||||
//! validating constructor would reject — the indicators must never panic on
|
||||
//! any of them, streaming or batched.
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use wickra_core::{
|
||||
BatchExt, Indicator, Level, Microprice, OrderBook, OrderBookImbalanceFull,
|
||||
OrderBookImbalanceTop1, OrderBookImbalanceTopN, QuotedSpread,
|
||||
};
|
||||
|
||||
#[inline(never)]
|
||||
fn drive<I>(make: impl Fn() -> I, books: &[OrderBook])
|
||||
where
|
||||
I: Indicator<Input = OrderBook, Output = f64> + BatchExt,
|
||||
{
|
||||
let mut streaming = make();
|
||||
for book in books {
|
||||
let _ = streaming.update(book.clone());
|
||||
}
|
||||
let _ = make().batch(books);
|
||||
}
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
let floats: Vec<f64> = data
|
||||
.chunks_exact(8)
|
||||
.map(|c| f64::from_le_bytes(c.try_into().expect("8 bytes")))
|
||||
.collect();
|
||||
let levels: Vec<Level> = floats
|
||||
.chunks_exact(2)
|
||||
.map(|c| Level::new_unchecked(c[0], c[1]))
|
||||
.collect();
|
||||
// Group levels into snapshots of up to four levels (split into bids / asks).
|
||||
let books: Vec<OrderBook> = levels
|
||||
.chunks(4)
|
||||
.map(|chunk| {
|
||||
let half = chunk.len() / 2;
|
||||
OrderBook::new_unchecked(chunk[..half].to_vec(), chunk[half..].to_vec())
|
||||
})
|
||||
.collect();
|
||||
|
||||
drive(OrderBookImbalanceTop1::new, &books);
|
||||
drive(|| OrderBookImbalanceTopN::new(3).unwrap(), &books);
|
||||
drive(OrderBookImbalanceFull::new, &books);
|
||||
drive(Microprice::new, &books);
|
||||
drive(QuotedSpread::new, &books);
|
||||
});
|
||||
Reference in New Issue
Block a user