nautilus_model/data/
stubs.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 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
16//! Type stubs to facilitate testing.
17
18use nautilus_core::UnixNanos;
19use rstest::fixture;
20
21use super::{
22    Bar, BarSpecification, BarType, InstrumentStatus, OrderBookDelta, OrderBookDeltas,
23    OrderBookDepth10, QuoteTick, TradeTick, DEPTH10_LEN,
24};
25use crate::{
26    data::order::BookOrder,
27    enums::{
28        AggregationSource, AggressorSide, BarAggregation, BookAction, MarketStatusAction,
29        OrderSide, PriceType,
30    },
31    identifiers::{InstrumentId, Symbol, TradeId, Venue},
32    types::{Price, Quantity},
33};
34
35impl Default for QuoteTick {
36    /// Creates a new default [`QuoteTick`] instance for testing.
37    fn default() -> Self {
38        Self {
39            instrument_id: InstrumentId::from("AUDUSD.SIM"),
40            bid_price: Price::from("1.00000"),
41            ask_price: Price::from("1.00000"),
42            bid_size: Quantity::from(100_000),
43            ask_size: Quantity::from(100_000),
44            ts_event: UnixNanos::default(),
45            ts_init: UnixNanos::default(),
46        }
47    }
48}
49
50impl Default for TradeTick {
51    /// Creates a new default [`TradeTick`] instance for testing.
52    fn default() -> Self {
53        TradeTick {
54            instrument_id: InstrumentId::from("AUDUSD.SIM"),
55            price: Price::from("1.00000"),
56            size: Quantity::from(100_000),
57            aggressor_side: AggressorSide::Buyer,
58            trade_id: TradeId::new("123456789"),
59            ts_event: UnixNanos::default(),
60            ts_init: UnixNanos::default(),
61        }
62    }
63}
64
65impl Default for Bar {
66    /// Creates a new default [`Bar`] instance for testing.
67    fn default() -> Self {
68        Self {
69            bar_type: BarType::from("AUDUSD.SIM-1-MINUTE-LAST-INTERNAL"),
70            open: Price::from("1.00010"),
71            high: Price::from("1.00020"),
72            low: Price::from("1.00000"),
73            close: Price::from("1.00010"),
74            volume: Quantity::from(100_000),
75            ts_event: UnixNanos::default(),
76            ts_init: UnixNanos::default(),
77        }
78    }
79}
80
81#[fixture]
82pub fn stub_delta() -> OrderBookDelta {
83    let instrument_id = InstrumentId::from("AAPL.XNAS");
84    let action = BookAction::Add;
85    let price = Price::from("100.00");
86    let size = Quantity::from("10");
87    let side = OrderSide::Buy;
88    let order_id = 123_456;
89    let flags = 0;
90    let sequence = 1;
91    let ts_event = 1;
92    let ts_init = 2;
93
94    let order = BookOrder::new(side, price, size, order_id);
95    OrderBookDelta::new(
96        instrument_id,
97        action,
98        order,
99        flags,
100        sequence,
101        ts_event.into(),
102        ts_init.into(),
103    )
104}
105
106#[fixture]
107pub fn stub_deltas() -> OrderBookDeltas {
108    let instrument_id = InstrumentId::from("AAPL.XNAS");
109    let flags = 32; // Snapshot flag
110    let sequence = 0;
111    let ts_event = 1;
112    let ts_init = 2;
113
114    let delta0 = OrderBookDelta::clear(instrument_id, sequence, ts_event.into(), ts_init.into());
115    let delta1 = OrderBookDelta::new(
116        instrument_id,
117        BookAction::Add,
118        BookOrder::new(
119            OrderSide::Sell,
120            Price::from("102.00"),
121            Quantity::from("300"),
122            1,
123        ),
124        flags,
125        sequence,
126        ts_event.into(),
127        ts_init.into(),
128    );
129    let delta2 = OrderBookDelta::new(
130        instrument_id,
131        BookAction::Add,
132        BookOrder::new(
133            OrderSide::Sell,
134            Price::from("101.00"),
135            Quantity::from("200"),
136            2,
137        ),
138        flags,
139        sequence,
140        ts_event.into(),
141        ts_init.into(),
142    );
143    let delta3 = OrderBookDelta::new(
144        instrument_id,
145        BookAction::Add,
146        BookOrder::new(
147            OrderSide::Sell,
148            Price::from("100.00"),
149            Quantity::from("100"),
150            3,
151        ),
152        flags,
153        sequence,
154        ts_event.into(),
155        ts_init.into(),
156    );
157    let delta4 = OrderBookDelta::new(
158        instrument_id,
159        BookAction::Add,
160        BookOrder::new(
161            OrderSide::Buy,
162            Price::from("99.00"),
163            Quantity::from("100"),
164            4,
165        ),
166        flags,
167        sequence,
168        ts_event.into(),
169        ts_init.into(),
170    );
171    let delta5 = OrderBookDelta::new(
172        instrument_id,
173        BookAction::Add,
174        BookOrder::new(
175            OrderSide::Buy,
176            Price::from("98.00"),
177            Quantity::from("200"),
178            5,
179        ),
180        flags,
181        sequence,
182        ts_event.into(),
183        ts_init.into(),
184    );
185    let delta6 = OrderBookDelta::new(
186        instrument_id,
187        BookAction::Add,
188        BookOrder::new(
189            OrderSide::Buy,
190            Price::from("97.00"),
191            Quantity::from("300"),
192            6,
193        ),
194        flags,
195        sequence,
196        ts_event.into(),
197        ts_init.into(),
198    );
199
200    let deltas = vec![delta0, delta1, delta2, delta3, delta4, delta5, delta6];
201
202    OrderBookDeltas::new(instrument_id, deltas)
203}
204
205#[fixture]
206pub fn stub_depth10() -> OrderBookDepth10 {
207    let instrument_id = InstrumentId::from("AAPL.XNAS");
208    let flags = 0;
209    let sequence = 0;
210    let ts_event = 1;
211    let ts_init = 2;
212
213    let mut bids: [BookOrder; DEPTH10_LEN] = [BookOrder::default(); DEPTH10_LEN];
214    let mut asks: [BookOrder; DEPTH10_LEN] = [BookOrder::default(); DEPTH10_LEN];
215
216    // Create bids
217    let mut price = 99.00;
218    let mut quantity = 100.0;
219    let mut order_id = 1;
220
221    #[allow(clippy::needless_range_loop)]
222    for i in 0..DEPTH10_LEN {
223        let order = BookOrder::new(
224            OrderSide::Buy,
225            Price::new(price, 2),
226            Quantity::new(quantity, 0),
227            order_id,
228        );
229
230        bids[i] = order;
231
232        price -= 1.0;
233        quantity += 100.0;
234        order_id += 1;
235    }
236
237    // Create asks
238    let mut price = 100.00;
239    let mut quantity = 100.0;
240    let mut order_id = 11;
241
242    #[allow(clippy::needless_range_loop)]
243    for i in 0..DEPTH10_LEN {
244        let order = BookOrder::new(
245            OrderSide::Sell,
246            Price::new(price, 2),
247            Quantity::new(quantity, 0),
248            order_id,
249        );
250
251        asks[i] = order;
252
253        price += 1.0;
254        quantity += 100.0;
255        order_id += 1;
256    }
257
258    let bid_counts: [u32; DEPTH10_LEN] = [1; DEPTH10_LEN];
259    let ask_counts: [u32; DEPTH10_LEN] = [1; DEPTH10_LEN];
260
261    OrderBookDepth10::new(
262        instrument_id,
263        bids,
264        asks,
265        bid_counts,
266        ask_counts,
267        flags,
268        sequence,
269        ts_event.into(),
270        ts_init.into(),
271    )
272}
273
274#[fixture]
275pub fn stub_book_order() -> BookOrder {
276    let price = Price::from("100.00");
277    let size = Quantity::from("10");
278    let side = OrderSide::Buy;
279    let order_id = 123_456;
280
281    BookOrder::new(side, price, size, order_id)
282}
283
284#[fixture]
285pub fn quote_ethusdt_binance() -> QuoteTick {
286    QuoteTick {
287        instrument_id: InstrumentId::from("ETHUSDT-PERP.BINANCE"),
288        bid_price: Price::from("10000.0000"),
289        ask_price: Price::from("10001.0000"),
290        bid_size: Quantity::from("1.00000000"),
291        ask_size: Quantity::from("1.00000000"),
292        ts_event: UnixNanos::default(),
293        ts_init: UnixNanos::from(1),
294    }
295}
296
297#[fixture]
298pub fn quote_audusd() -> QuoteTick {
299    QuoteTick {
300        instrument_id: InstrumentId::from("AUD/USD.SIM"),
301        bid_price: Price::from("100.0000"),
302        ask_price: Price::from("101.0000"),
303        bid_size: Quantity::from("1.00000000"),
304        ask_size: Quantity::from("1.00000000"),
305        ts_event: UnixNanos::default(),
306        ts_init: UnixNanos::from(1),
307    }
308}
309
310#[fixture]
311pub fn stub_trade_ethusdt_buyer() -> TradeTick {
312    TradeTick {
313        instrument_id: InstrumentId::from("ETHUSDT-PERP.BINANCE"),
314        price: Price::from("10000.0000"),
315        size: Quantity::from("1.00000000"),
316        aggressor_side: AggressorSide::Buyer,
317        trade_id: TradeId::new("123456789"),
318        ts_event: UnixNanos::default(),
319        ts_init: UnixNanos::from(1),
320    }
321}
322
323#[fixture]
324pub fn stub_bar() -> Bar {
325    let instrument_id = InstrumentId {
326        symbol: Symbol::new("AUD/USD"),
327        venue: Venue::new("SIM"),
328    };
329    let bar_spec = BarSpecification::new(1, BarAggregation::Minute, PriceType::Bid);
330    let bar_type = BarType::Standard {
331        instrument_id,
332        spec: bar_spec,
333        aggregation_source: AggregationSource::External,
334    };
335    Bar {
336        bar_type,
337        open: Price::from("1.00002"),
338        high: Price::from("1.00004"),
339        low: Price::from("1.00001"),
340        close: Price::from("1.00003"),
341        volume: Quantity::from("100000"),
342        ts_event: UnixNanos::default(),
343        ts_init: UnixNanos::from(1),
344    }
345}
346
347#[fixture]
348pub fn stub_instrument_status() -> InstrumentStatus {
349    let instrument_id = InstrumentId::from("MSFT.XNAS");
350    InstrumentStatus::new(
351        instrument_id,
352        MarketStatusAction::Trading,
353        UnixNanos::from(1),
354        UnixNanos::from(2),
355        None,
356        None,
357        None,
358        None,
359        None,
360    )
361}
362
363pub struct OrderBookDeltaTestBuilder {
364    instrument_id: InstrumentId,
365    action: Option<BookAction>,
366    book_order: Option<BookOrder>,
367    flags: Option<u8>,
368    sequence: Option<u64>,
369}
370
371impl OrderBookDeltaTestBuilder {
372    pub fn new(instrument_id: InstrumentId) -> Self {
373        Self {
374            instrument_id,
375            action: None,
376            book_order: None,
377            flags: None,
378            sequence: None,
379        }
380    }
381
382    pub fn book_action(&mut self, action: BookAction) -> &mut Self {
383        self.action = Some(action);
384        self
385    }
386
387    fn get_book_action(&self) -> BookAction {
388        self.action.unwrap_or(BookAction::Add)
389    }
390
391    pub fn book_order(&mut self, book_order: BookOrder) -> &mut Self {
392        self.book_order = Some(book_order);
393        self
394    }
395
396    fn get_book_order(&self) -> BookOrder {
397        self.book_order.unwrap_or(BookOrder::new(
398            OrderSide::Sell,
399            Price::from("1500.00"),
400            Quantity::from("1"),
401            1,
402        ))
403    }
404
405    pub fn flags(&mut self, flags: u8) -> &mut Self {
406        self.flags = Some(flags);
407        self
408    }
409
410    fn get_flags(&self) -> u8 {
411        self.flags.unwrap_or(0)
412    }
413
414    pub fn sequence(&mut self, sequence: u64) -> &mut Self {
415        self.sequence = Some(sequence);
416        self
417    }
418
419    fn get_sequence(&self) -> u64 {
420        self.sequence.unwrap_or(1)
421    }
422
423    pub fn build(&self) -> OrderBookDelta {
424        OrderBookDelta::new(
425            self.instrument_id,
426            self.get_book_action(),
427            self.get_book_order(),
428            self.get_flags(),
429            self.get_sequence(),
430            UnixNanos::from(1),
431            UnixNanos::from(2),
432        )
433    }
434}