use crate::microstructure::OrderBook;
use crate::traits::Indicator;
#[derive(Debug, Clone, Default)]
pub struct OrderBookImbalanceTop1 {
has_emitted: bool,
}
impl OrderBookImbalanceTop1 {
pub const fn new() -> Self {
Self { has_emitted: false }
}
}
impl Indicator for OrderBookImbalanceTop1 {
type Input = OrderBook;
type Output = f64;
fn update(&mut self, book: OrderBook) -> Option<f64> {
self.has_emitted = true;
let (Some(bid), Some(ask)) = (book.best_bid(), book.best_ask()) else {
return Some(0.0);
};
let total = bid.size + ask.size;
if total <= 0.0 {
return Some(0.0);
}
Some((bid.size - ask.size) / total)
}
fn reset(&mut self) {
self.has_emitted = false;
}
fn warmup_period(&self) -> usize {
1
}
fn is_ready(&self) -> bool {
self.has_emitted
}
fn name(&self) -> &'static str {
"OrderBookImbalanceTop1"
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::microstructure::Level;
use crate::traits::BatchExt;
fn book(bids: &[(f64, f64)], asks: &[(f64, f64)]) -> OrderBook {
let to_levels = |xs: &[(f64, f64)]| {
xs.iter()
.map(|&(p, s)| Level::new(p, s).unwrap())
.collect::<Vec<_>>()
};
OrderBook::new(to_levels(bids), to_levels(asks)).unwrap()
}
#[test]
fn accessors_and_metadata() {
let obi = OrderBookImbalanceTop1::new();
assert_eq!(obi.name(), "OrderBookImbalanceTop1");
assert_eq!(obi.warmup_period(), 1);
assert!(!obi.is_ready());
}
#[test]
fn balanced_top_is_zero() {
let mut obi = OrderBookImbalanceTop1::new();
assert_eq!(
obi.update(book(&[(100.0, 2.0)], &[(101.0, 2.0)])),
Some(0.0)
);
assert!(obi.is_ready());
}
#[test]
fn bid_heavy_is_positive() {
let mut obi = OrderBookImbalanceTop1::new();
assert_eq!(
obi.update(book(&[(100.0, 3.0)], &[(101.0, 1.0)])),
Some(0.5)
);
}
#[test]
fn ask_heavy_is_negative() {
let mut obi = OrderBookImbalanceTop1::new();
assert_eq!(
obi.update(book(&[(100.0, 1.0)], &[(101.0, 3.0)])),
Some(-0.5)
);
}
#[test]
fn zero_size_top_is_zero() {
let mut obi = OrderBookImbalanceTop1::new();
assert_eq!(
obi.update(book(&[(100.0, 0.0)], &[(101.0, 0.0)])),
Some(0.0)
);
}
#[test]
fn empty_book_is_zero() {
let mut obi = OrderBookImbalanceTop1::new();
assert_eq!(
obi.update(OrderBook::new_unchecked(vec![], vec![])),
Some(0.0)
);
}
#[test]
fn batch_equals_streaming() {
let books: Vec<OrderBook> = (0..20)
.map(|i| {
let bid = 1.0 + f64::from(i % 5);
book(&[(100.0, bid)], &[(101.0, 2.0)])
})
.collect();
let mut a = OrderBookImbalanceTop1::new();
let mut b = OrderBookImbalanceTop1::new();
assert_eq!(
a.batch(&books),
books
.iter()
.map(|x| b.update(x.clone()))
.collect::<Vec<_>>()
);
}
#[test]
fn reset_clears_state() {
let mut obi = OrderBookImbalanceTop1::new();
obi.update(book(&[(100.0, 1.0)], &[(101.0, 1.0)]));
assert!(obi.is_ready());
obi.reset();
assert!(!obi.is_ready());
}
}