Skip to main content

nautilus_model/ffi/orderbook/
book.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use std::{
17    ffi::c_char,
18    ops::{Deref, DerefMut},
19};
20
21use nautilus_core::ffi::{abort_on_panic, cvec::CVec, string::str_to_cstr};
22
23use super::level::BookLevel_API;
24use crate::{
25    data::{
26        BookOrder, OrderBookDelta, OrderBookDeltas_API, OrderBookDepth10, QuoteTick, TradeTick,
27    },
28    enums::{BookType, OrderSide, OrderSideSpecified},
29    identifiers::InstrumentId,
30    orderbook::{OrderBook, analysis::book_check_integrity, ladder::BookPrice},
31    types::{ERROR_PRICE, Price, Quantity, price::PriceRaw},
32};
33
34/// C compatible Foreign Function Interface (FFI) for an underlying `OrderBook`.
35///
36/// This struct wraps `OrderBook` in a way that makes it compatible with C function
37/// calls, enabling interaction with `OrderBook` in a C environment.
38///
39/// It implements the `Deref` trait, allowing instances of `OrderBook_API` to be
40/// dereferenced to `OrderBook`, providing access to `OrderBook`'s methods without
41/// having to manually access the underlying `OrderBook` instance.
42#[repr(C)]
43#[derive(Debug)]
44#[allow(non_camel_case_types)]
45pub struct OrderBook_API(Box<OrderBook>);
46
47impl Deref for OrderBook_API {
48    type Target = OrderBook;
49
50    fn deref(&self) -> &Self::Target {
51        &self.0
52    }
53}
54
55impl DerefMut for OrderBook_API {
56    fn deref_mut(&mut self) -> &mut Self::Target {
57        &mut self.0
58    }
59}
60
61#[unsafe(no_mangle)]
62pub extern "C" fn orderbook_new(instrument_id: InstrumentId, book_type: BookType) -> OrderBook_API {
63    OrderBook_API(Box::new(OrderBook::new(instrument_id, book_type)))
64}
65
66#[unsafe(no_mangle)]
67pub extern "C" fn orderbook_drop(book: OrderBook_API) {
68    drop(book); // Memory freed here
69}
70
71#[unsafe(no_mangle)]
72pub extern "C" fn orderbook_reset(book: &mut OrderBook_API) {
73    book.reset();
74}
75
76#[unsafe(no_mangle)]
77pub extern "C" fn orderbook_instrument_id(book: &OrderBook_API) -> InstrumentId {
78    book.instrument_id
79}
80
81#[unsafe(no_mangle)]
82pub extern "C" fn orderbook_book_type(book: &OrderBook_API) -> BookType {
83    book.book_type
84}
85
86#[unsafe(no_mangle)]
87pub extern "C" fn orderbook_sequence(book: &OrderBook_API) -> u64 {
88    book.sequence
89}
90
91#[unsafe(no_mangle)]
92pub extern "C" fn orderbook_ts_last(book: &OrderBook_API) -> u64 {
93    book.ts_last.into()
94}
95
96#[unsafe(no_mangle)]
97pub extern "C" fn orderbook_update_count(book: &OrderBook_API) -> u64 {
98    book.update_count
99}
100
101#[unsafe(no_mangle)]
102#[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
103pub extern "C" fn orderbook_add(
104    book: &mut OrderBook_API,
105    order: BookOrder,
106    flags: u8,
107    sequence: u64,
108    ts_event: u64,
109) {
110    book.add(order, flags, sequence, ts_event.into());
111}
112
113#[unsafe(no_mangle)]
114#[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
115pub extern "C" fn orderbook_update(
116    book: &mut OrderBook_API,
117    order: BookOrder,
118    flags: u8,
119    sequence: u64,
120    ts_event: u64,
121) {
122    book.update(order, flags, sequence, ts_event.into());
123}
124
125#[unsafe(no_mangle)]
126#[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
127pub extern "C" fn orderbook_delete(
128    book: &mut OrderBook_API,
129    order: BookOrder,
130    flags: u8,
131    sequence: u64,
132    ts_event: u64,
133) {
134    book.delete(order, flags, sequence, ts_event.into());
135}
136
137#[unsafe(no_mangle)]
138pub extern "C" fn orderbook_clear(book: &mut OrderBook_API, sequence: u64, ts_event: u64) {
139    book.clear(sequence, ts_event.into());
140}
141
142#[unsafe(no_mangle)]
143pub extern "C" fn orderbook_clear_bids(book: &mut OrderBook_API, sequence: u64, ts_event: u64) {
144    book.clear_bids(sequence, ts_event.into());
145}
146
147#[unsafe(no_mangle)]
148pub extern "C" fn orderbook_clear_asks(book: &mut OrderBook_API, sequence: u64, ts_event: u64) {
149    book.clear_asks(sequence, ts_event.into());
150}
151
152#[unsafe(no_mangle)]
153pub extern "C" fn orderbook_apply_delta(book: &mut OrderBook_API, delta: &OrderBookDelta) {
154    if let Err(e) = book.apply_delta_unchecked(delta) {
155        log::error!("Failed to apply order book delta: {e}");
156    }
157}
158
159#[unsafe(no_mangle)]
160pub extern "C" fn orderbook_apply_deltas(book: &mut OrderBook_API, deltas: &OrderBookDeltas_API) {
161    // Clone will actually copy the contents of the `deltas` vec
162    if let Err(e) = book.apply_deltas_unchecked(deltas) {
163        log::error!("Failed to apply order book deltas: {e}");
164    }
165}
166
167/// Creates an `OrderBookDeltas` snapshot from the current order book state.
168///
169/// This is the reverse operation of `orderbook_apply_deltas`: it converts the current book state
170/// back into a snapshot format with a `Clear` delta followed by `Add` deltas for all orders.
171///
172/// # Parameters
173///
174/// * `book` - The order book to convert.
175/// * `sequence` - The message sequence number for the snapshot.
176/// * `ts_event` - UNIX timestamp (nanoseconds) when the book event occurred.
177/// * `ts_init` - UNIX timestamp (nanoseconds) when the instance was created.
178///
179/// # Returns
180///
181/// An `OrderBookDeltas_API` containing a snapshot of the current order book state.
182#[unsafe(no_mangle)]
183pub extern "C" fn orderbook_to_snapshot_deltas(
184    book: &OrderBook_API,
185    ts_event: u64,
186    ts_init: u64,
187) -> OrderBookDeltas_API {
188    use nautilus_core::UnixNanos;
189    OrderBookDeltas_API::new(book.to_deltas(UnixNanos::from(ts_event), UnixNanos::from(ts_init)))
190}
191
192#[unsafe(no_mangle)]
193pub extern "C" fn orderbook_apply_depth(book: &mut OrderBook_API, depth: &OrderBookDepth10) {
194    if let Err(e) = book.apply_depth_unchecked(depth) {
195        log::error!("Failed to apply order book depth: {e}");
196    }
197}
198
199#[unsafe(no_mangle)]
200pub extern "C" fn orderbook_bids(book: &mut OrderBook_API) -> CVec {
201    book.bids
202        .levels
203        .values()
204        .map(|level| BookLevel_API::new(level.clone()))
205        .collect::<Vec<BookLevel_API>>()
206        .into()
207}
208
209#[unsafe(no_mangle)]
210pub extern "C" fn orderbook_asks(book: &mut OrderBook_API) -> CVec {
211    book.asks
212        .levels
213        .values()
214        .map(|level| BookLevel_API::new(level.clone()))
215        .collect::<Vec<BookLevel_API>>()
216        .into()
217}
218
219#[unsafe(no_mangle)]
220#[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
221pub extern "C" fn orderbook_bids_down_to(
222    book: &mut OrderBook_API,
223    price_raw: PriceRaw,
224    price_prec: u8,
225) -> CVec {
226    let price = Price::from_raw(price_raw, price_prec);
227    let bound = BookPrice::new(price, OrderSideSpecified::Buy);
228    book.bids
229        .levels
230        .range(..=bound)
231        .map(|(_, level)| BookLevel_API::new(level.clone()))
232        .collect::<Vec<BookLevel_API>>()
233        .into()
234}
235
236#[unsafe(no_mangle)]
237#[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
238pub extern "C" fn orderbook_asks_up_to(
239    book: &mut OrderBook_API,
240    price_raw: PriceRaw,
241    price_prec: u8,
242) -> CVec {
243    let price = Price::from_raw(price_raw, price_prec);
244    let bound = BookPrice::new(price, OrderSideSpecified::Sell);
245    book.asks
246        .levels
247        .range(..=bound)
248        .map(|(_, level)| BookLevel_API::new(level.clone()))
249        .collect::<Vec<BookLevel_API>>()
250        .into()
251}
252
253#[unsafe(no_mangle)]
254pub extern "C" fn orderbook_has_bid(book: &mut OrderBook_API) -> u8 {
255    u8::from(book.has_bid())
256}
257
258#[unsafe(no_mangle)]
259pub extern "C" fn orderbook_has_ask(book: &mut OrderBook_API) -> u8 {
260    u8::from(book.has_ask())
261}
262
263/// # Panics
264///
265/// Panics if there are no bid orders for best bid price.
266#[unsafe(no_mangle)]
267#[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
268pub extern "C" fn orderbook_best_bid_price(book: &mut OrderBook_API) -> Price {
269    abort_on_panic(|| {
270        book.best_bid_price()
271            .expect("Error: No bid orders for best bid price")
272    })
273}
274
275/// # Panics
276///
277/// Panics if there are no ask orders for best ask price.
278#[unsafe(no_mangle)]
279#[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
280pub extern "C" fn orderbook_best_ask_price(book: &mut OrderBook_API) -> Price {
281    abort_on_panic(|| {
282        book.best_ask_price()
283            .expect("Error: No ask orders for best ask price")
284    })
285}
286
287/// # Panics
288///
289/// Panics if there are no bid orders for best bid size.
290#[unsafe(no_mangle)]
291#[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
292pub extern "C" fn orderbook_best_bid_size(book: &mut OrderBook_API) -> Quantity {
293    abort_on_panic(|| {
294        book.best_bid_size()
295            .expect("Error: No bid orders for best bid size")
296    })
297}
298
299/// # Panics
300///
301/// Panics if there are no ask orders for best ask size.
302#[unsafe(no_mangle)]
303#[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
304pub extern "C" fn orderbook_best_ask_size(book: &mut OrderBook_API) -> Quantity {
305    abort_on_panic(|| {
306        book.best_ask_size()
307            .expect("Error: No ask orders for best ask size")
308    })
309}
310
311/// # Panics
312///
313/// Panics if unable to calculate spread (requires at least one bid and one ask).
314#[unsafe(no_mangle)]
315pub extern "C" fn orderbook_spread(book: &mut OrderBook_API) -> f64 {
316    abort_on_panic(|| {
317        book.spread()
318            .expect("Error: Unable to calculate `spread` (no bid or ask)")
319    })
320}
321
322/// # Panics
323///
324/// Panics if unable to calculate midpoint (requires at least one bid and one ask).
325#[unsafe(no_mangle)]
326pub extern "C" fn orderbook_midpoint(book: &mut OrderBook_API) -> f64 {
327    abort_on_panic(|| {
328        book.midpoint()
329            .expect("Error: Unable to calculate `midpoint` (no bid or ask)")
330    })
331}
332
333#[unsafe(no_mangle)]
334#[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
335pub extern "C" fn orderbook_get_avg_px_for_quantity(
336    book: &mut OrderBook_API,
337    qty: Quantity,
338    order_side: OrderSide,
339) -> f64 {
340    book.get_avg_px_for_quantity(qty, order_side)
341}
342
343#[unsafe(no_mangle)]
344#[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
345pub extern "C" fn orderbook_get_worst_px_for_quantity(
346    book: &mut OrderBook_API,
347    qty: Quantity,
348    order_side: OrderSide,
349) -> Price {
350    book.get_worst_px_for_quantity(qty, order_side)
351        .unwrap_or(ERROR_PRICE)
352}
353
354#[unsafe(no_mangle)]
355#[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
356pub extern "C" fn orderbook_get_quantity_for_price(
357    book: &mut OrderBook_API,
358    price: Price,
359    order_side: OrderSide,
360) -> f64 {
361    book.get_quantity_for_price(price, order_side)
362}
363
364#[unsafe(no_mangle)]
365#[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
366pub extern "C" fn orderbook_get_quantity_at_level(
367    book: &OrderBook_API,
368    price: Price,
369    order_side: OrderSide,
370    size_precision: u8,
371) -> Quantity {
372    book.get_quantity_at_level(price, order_side, size_precision)
373}
374
375/// Updates the order book with a quote tick.
376///
377/// # Panics
378///
379/// Panics if book type is not `L1_MBP`.
380#[unsafe(no_mangle)]
381pub extern "C" fn orderbook_update_quote_tick(book: &mut OrderBook_API, quote: &QuoteTick) {
382    book.update_quote_tick(quote).unwrap();
383}
384
385/// Updates the order book with a trade tick.
386///
387/// # Panics
388///
389/// Panics if book type is not `L1_MBP`.
390#[unsafe(no_mangle)]
391pub extern "C" fn orderbook_update_trade_tick(book: &mut OrderBook_API, trade: &TradeTick) {
392    book.update_trade_tick(trade).unwrap();
393}
394
395#[unsafe(no_mangle)]
396#[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
397pub extern "C" fn orderbook_simulate_fills(book: &OrderBook_API, order: BookOrder) -> CVec {
398    book.simulate_fills(&order).into()
399}
400
401#[unsafe(no_mangle)]
402#[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
403pub extern "C" fn orderbook_get_all_crossed_levels(
404    book: &OrderBook_API,
405    order_side: OrderSide,
406    price: Price,
407    size_precision: u8,
408) -> CVec {
409    book.get_all_crossed_levels(order_side, price, size_precision)
410        .into()
411}
412
413#[unsafe(no_mangle)]
414pub extern "C" fn orderbook_check_integrity(book: &OrderBook_API) -> u8 {
415    u8::from(book_check_integrity(book).is_ok())
416}
417
418#[unsafe(no_mangle)]
419pub extern "C" fn vec_drop_fills(v: CVec) {
420    if v.ptr.is_null() {
421        return;
422    }
423
424    let CVec { ptr, len, cap } = v;
425    let data: Vec<(Price, Quantity)> =
426        unsafe { Vec::from_raw_parts(ptr.cast::<(Price, Quantity)>(), len, cap) };
427    drop(data); // Memory freed here
428}
429
430/// Returns a pretty printed `OrderBook` number of levels per side, as a C string pointer.
431#[unsafe(no_mangle)]
432pub extern "C" fn orderbook_pprint_to_cstr(
433    book: &OrderBook_API,
434    num_levels: usize,
435) -> *const c_char {
436    str_to_cstr(&book.pprint(num_levels, None))
437}