1use std::sync::Arc;
19
20use nautilus_core::{Params, UnixNanos};
21use rstest::fixture;
22use serde::{Deserialize, Serialize};
23
24use super::{
25 Bar, BarSpecification, BarType, CustomData, CustomDataTrait, DEPTH10_LEN, DataType, HasTsInit,
26 InstrumentStatus, OrderBookDelta, OrderBookDeltas, OrderBookDepth10, QuoteTick, TradeTick,
27 close::InstrumentClose, register_custom_data_json,
28};
29use crate::{
30 data::order::BookOrder,
31 enums::{
32 AggregationSource, AggressorSide, BarAggregation, BookAction, InstrumentCloseType,
33 MarketStatusAction, OrderSide, PriceType,
34 },
35 identifiers::{InstrumentId, Symbol, TradeId, Venue},
36 types::{Price, Quantity},
37};
38
39impl Default for QuoteTick {
40 fn default() -> Self {
42 Self {
43 instrument_id: InstrumentId::from("AUDUSD.SIM"),
44 bid_price: Price::from("1.00000"),
45 ask_price: Price::from("1.00000"),
46 bid_size: Quantity::from(100_000),
47 ask_size: Quantity::from(100_000),
48 ts_event: UnixNanos::default(),
49 ts_init: UnixNanos::default(),
50 }
51 }
52}
53
54impl Default for TradeTick {
55 fn default() -> Self {
57 Self {
58 instrument_id: InstrumentId::from("AUDUSD.SIM"),
59 price: Price::from("1.00000"),
60 size: Quantity::from(100_000),
61 aggressor_side: AggressorSide::Buyer,
62 trade_id: TradeId::new("123456789"),
63 ts_event: UnixNanos::default(),
64 ts_init: UnixNanos::default(),
65 }
66 }
67}
68
69impl Default for Bar {
70 fn default() -> Self {
72 Self {
73 bar_type: BarType::from("AUDUSD.SIM-1-MINUTE-LAST-INTERNAL"),
74 open: Price::from("1.00010"),
75 high: Price::from("1.00020"),
76 low: Price::from("1.00000"),
77 close: Price::from("1.00010"),
78 volume: Quantity::from(100_000),
79 ts_event: UnixNanos::default(),
80 ts_init: UnixNanos::default(),
81 }
82 }
83}
84
85#[fixture]
86pub fn stub_delta() -> OrderBookDelta {
87 let instrument_id = InstrumentId::from("AAPL.XNAS");
88 let action = BookAction::Add;
89 let price = Price::from("100.00");
90 let size = Quantity::from("10");
91 let side = OrderSide::Buy;
92 let order_id = 123_456;
93 let flags = 0;
94 let sequence = 1;
95 let ts_event = 1;
96 let ts_init = 2;
97
98 let order = BookOrder::new(side, price, size, order_id);
99 OrderBookDelta::new(
100 instrument_id,
101 action,
102 order,
103 flags,
104 sequence,
105 ts_event.into(),
106 ts_init.into(),
107 )
108}
109
110#[fixture]
111pub fn stub_deltas() -> OrderBookDeltas {
112 let instrument_id = InstrumentId::from("AAPL.XNAS");
113 let flags = 32; let sequence = 0;
115 let ts_event = 1;
116 let ts_init = 2;
117
118 let delta0 = OrderBookDelta::clear(instrument_id, sequence, ts_event.into(), ts_init.into());
119 let delta1 = OrderBookDelta::new(
120 instrument_id,
121 BookAction::Add,
122 BookOrder::new(
123 OrderSide::Sell,
124 Price::from("102.00"),
125 Quantity::from("300"),
126 1,
127 ),
128 flags,
129 sequence,
130 ts_event.into(),
131 ts_init.into(),
132 );
133 let delta2 = OrderBookDelta::new(
134 instrument_id,
135 BookAction::Add,
136 BookOrder::new(
137 OrderSide::Sell,
138 Price::from("101.00"),
139 Quantity::from("200"),
140 2,
141 ),
142 flags,
143 sequence,
144 ts_event.into(),
145 ts_init.into(),
146 );
147 let delta3 = OrderBookDelta::new(
148 instrument_id,
149 BookAction::Add,
150 BookOrder::new(
151 OrderSide::Sell,
152 Price::from("100.00"),
153 Quantity::from("100"),
154 3,
155 ),
156 flags,
157 sequence,
158 ts_event.into(),
159 ts_init.into(),
160 );
161 let delta4 = OrderBookDelta::new(
162 instrument_id,
163 BookAction::Add,
164 BookOrder::new(
165 OrderSide::Buy,
166 Price::from("99.00"),
167 Quantity::from("100"),
168 4,
169 ),
170 flags,
171 sequence,
172 ts_event.into(),
173 ts_init.into(),
174 );
175 let delta5 = OrderBookDelta::new(
176 instrument_id,
177 BookAction::Add,
178 BookOrder::new(
179 OrderSide::Buy,
180 Price::from("98.00"),
181 Quantity::from("200"),
182 5,
183 ),
184 flags,
185 sequence,
186 ts_event.into(),
187 ts_init.into(),
188 );
189 let delta6 = OrderBookDelta::new(
190 instrument_id,
191 BookAction::Add,
192 BookOrder::new(
193 OrderSide::Buy,
194 Price::from("97.00"),
195 Quantity::from("300"),
196 6,
197 ),
198 flags,
199 sequence,
200 ts_event.into(),
201 ts_init.into(),
202 );
203
204 let deltas = vec![delta0, delta1, delta2, delta3, delta4, delta5, delta6];
205
206 OrderBookDeltas::new(instrument_id, deltas)
207}
208
209#[fixture]
210pub fn stub_depth10() -> OrderBookDepth10 {
211 let instrument_id = InstrumentId::from("AAPL.XNAS");
212 let flags = 0;
213 let sequence = 0;
214 let ts_event = 1;
215 let ts_init = 2;
216
217 let mut bids: [BookOrder; DEPTH10_LEN] = [BookOrder::default(); DEPTH10_LEN];
218 let mut asks: [BookOrder; DEPTH10_LEN] = [BookOrder::default(); DEPTH10_LEN];
219
220 let mut price = 99.00;
222 let mut quantity = 100.0;
223 let mut order_id = 1;
224
225 #[allow(clippy::needless_range_loop)]
226 for i in 0..DEPTH10_LEN {
227 let order = BookOrder::new(
228 OrderSide::Buy,
229 Price::new(price, 2),
230 Quantity::new(quantity, 0),
231 order_id,
232 );
233
234 bids[i] = order;
235
236 price -= 1.0;
237 quantity += 100.0;
238 order_id += 1;
239 }
240
241 let mut price = 100.00;
243 let mut quantity = 100.0;
244 let mut order_id = 11;
245
246 #[allow(clippy::needless_range_loop)]
247 for i in 0..DEPTH10_LEN {
248 let order = BookOrder::new(
249 OrderSide::Sell,
250 Price::new(price, 2),
251 Quantity::new(quantity, 0),
252 order_id,
253 );
254
255 asks[i] = order;
256
257 price += 1.0;
258 quantity += 100.0;
259 order_id += 1;
260 }
261
262 let bid_counts: [u32; DEPTH10_LEN] = [1; DEPTH10_LEN];
263 let ask_counts: [u32; DEPTH10_LEN] = [1; DEPTH10_LEN];
264
265 OrderBookDepth10::new(
266 instrument_id,
267 bids,
268 asks,
269 bid_counts,
270 ask_counts,
271 flags,
272 sequence,
273 ts_event.into(),
274 ts_init.into(),
275 )
276}
277
278#[fixture]
279pub fn stub_book_order() -> BookOrder {
280 let price = Price::from("100.00");
281 let size = Quantity::from("10");
282 let side = OrderSide::Buy;
283 let order_id = 123_456;
284
285 BookOrder::new(side, price, size, order_id)
286}
287
288#[fixture]
289pub fn quote_ethusdt_binance() -> QuoteTick {
290 QuoteTick {
291 instrument_id: InstrumentId::from("ETHUSDT-PERP.BINANCE"),
292 bid_price: Price::from("10000.0000"),
293 ask_price: Price::from("10001.0000"),
294 bid_size: Quantity::from("1.00000000"),
295 ask_size: Quantity::from("1.00000000"),
296 ts_event: UnixNanos::default(),
297 ts_init: UnixNanos::from(1),
298 }
299}
300
301#[fixture]
302pub fn quote_audusd() -> QuoteTick {
303 QuoteTick {
304 instrument_id: InstrumentId::from("AUD/USD.SIM"),
305 bid_price: Price::from("100.0000"),
306 ask_price: Price::from("101.0000"),
307 bid_size: Quantity::from("1.00000000"),
308 ask_size: Quantity::from("1.00000000"),
309 ts_event: UnixNanos::default(),
310 ts_init: UnixNanos::from(1),
311 }
312}
313
314#[fixture]
315pub fn stub_trade_ethusdt_buyer() -> TradeTick {
316 TradeTick {
317 instrument_id: InstrumentId::from("ETHUSDT-PERP.BINANCE"),
318 price: Price::from("10000.0000"),
319 size: Quantity::from("1.00000000"),
320 aggressor_side: AggressorSide::Buyer,
321 trade_id: TradeId::new("123456789"),
322 ts_event: UnixNanos::default(),
323 ts_init: UnixNanos::from(1),
324 }
325}
326
327#[fixture]
328pub fn stub_bar() -> Bar {
329 let instrument_id = InstrumentId {
330 symbol: Symbol::new("AUD/USD"),
331 venue: Venue::new("SIM"),
332 };
333 let bar_spec = BarSpecification::new(1, BarAggregation::Minute, PriceType::Bid);
334 let bar_type = BarType::Standard {
335 instrument_id,
336 spec: bar_spec,
337 aggregation_source: AggregationSource::External,
338 };
339 Bar {
340 bar_type,
341 open: Price::from("1.00002"),
342 high: Price::from("1.00004"),
343 low: Price::from("1.00001"),
344 close: Price::from("1.00003"),
345 volume: Quantity::from("100000"),
346 ts_event: UnixNanos::default(),
347 ts_init: UnixNanos::from(1),
348 }
349}
350
351#[fixture]
352pub fn stub_instrument_status() -> InstrumentStatus {
353 let instrument_id = InstrumentId::from("MSFT.XNAS");
354 InstrumentStatus::new(
355 instrument_id,
356 MarketStatusAction::Trading,
357 UnixNanos::from(1),
358 UnixNanos::from(2),
359 None,
360 None,
361 None,
362 None,
363 None,
364 )
365}
366
367#[fixture]
368pub fn stub_instrument_close() -> InstrumentClose {
369 let instrument_id = InstrumentId::from("MSFT.XNAS");
370 InstrumentClose::new(
371 instrument_id,
372 Price::from("100.50"),
373 InstrumentCloseType::EndOfSession,
374 UnixNanos::from(1),
375 UnixNanos::from(2),
376 )
377}
378
379#[derive(Debug)]
380pub struct OrderBookDeltaTestBuilder {
381 instrument_id: InstrumentId,
382 action: Option<BookAction>,
383 book_order: Option<BookOrder>,
384 flags: Option<u8>,
385 sequence: Option<u64>,
386 ts_event: Option<UnixNanos>,
387}
388
389impl OrderBookDeltaTestBuilder {
390 pub fn new(instrument_id: InstrumentId) -> Self {
391 Self {
392 instrument_id,
393 action: None,
394 book_order: None,
395 flags: None,
396 sequence: None,
397 ts_event: None,
398 }
399 }
400
401 pub fn book_action(&mut self, action: BookAction) -> &mut Self {
402 self.action = Some(action);
403 self
404 }
405
406 fn get_book_action(&self) -> BookAction {
407 self.action.unwrap_or(BookAction::Add)
408 }
409
410 pub fn book_order(&mut self, book_order: BookOrder) -> &mut Self {
411 self.book_order = Some(book_order);
412 self
413 }
414
415 fn get_book_order(&self) -> BookOrder {
416 self.book_order.unwrap_or(BookOrder::new(
417 OrderSide::Sell,
418 Price::from("1500.00"),
419 Quantity::from("1"),
420 1,
421 ))
422 }
423
424 pub fn flags(&mut self, flags: u8) -> &mut Self {
425 self.flags = Some(flags);
426 self
427 }
428
429 fn get_flags(&self) -> u8 {
430 self.flags.unwrap_or(0)
431 }
432
433 pub fn sequence(&mut self, sequence: u64) -> &mut Self {
434 self.sequence = Some(sequence);
435 self
436 }
437
438 fn get_sequence(&self) -> u64 {
439 self.sequence.unwrap_or(1)
440 }
441
442 pub fn ts_event(&mut self, ts_event: UnixNanos) -> &mut Self {
443 self.ts_event = Some(ts_event);
444 self
445 }
446
447 pub fn build(&self) -> OrderBookDelta {
448 OrderBookDelta::new(
449 self.instrument_id,
450 self.get_book_action(),
451 self.get_book_order(),
452 self.get_flags(),
453 self.get_sequence(),
454 self.ts_event.unwrap_or(UnixNanos::from(1)),
455 UnixNanos::from(2),
456 )
457 }
458}
459
460#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
462pub struct StubCustomData {
463 pub ts_init: UnixNanos,
464 pub value: i64,
465}
466
467impl HasTsInit for StubCustomData {
468 fn ts_init(&self) -> UnixNanos {
469 self.ts_init
470 }
471}
472
473impl CustomDataTrait for StubCustomData {
474 fn type_name(&self) -> &'static str {
475 "StubCustomData"
476 }
477 fn as_any(&self) -> &dyn std::any::Any {
478 self
479 }
480 fn ts_event(&self) -> UnixNanos {
481 self.ts_init
482 }
483 fn to_json(&self) -> anyhow::Result<String> {
484 Ok(serde_json::to_string(self)?)
485 }
486 fn clone_arc(&self) -> Arc<dyn CustomDataTrait> {
487 Arc::new(self.clone())
488 }
489 fn eq_arc(&self, other: &dyn CustomDataTrait) -> bool {
490 if let Some(o) = other.as_any().downcast_ref::<Self>() {
491 self == o
492 } else {
493 false
494 }
495 }
496
497 fn type_name_static() -> &'static str {
498 "StubCustomData"
499 }
500 fn from_json(value: serde_json::Value) -> anyhow::Result<Arc<dyn CustomDataTrait>> {
501 let parsed: Self = serde_json::from_value(value)?;
502 Ok(Arc::new(parsed))
503 }
504}
505
506pub fn ensure_stub_custom_data_registered() {
508 let _ = register_custom_data_json::<StubCustomData>();
509}
510
511pub fn stub_custom_data(
513 ts_init: u64,
514 value: i64,
515 metadata: Option<Params>,
516 identifier: Option<String>,
517) -> CustomData {
518 ensure_stub_custom_data_registered();
519 let inner = StubCustomData {
520 ts_init: UnixNanos::from(ts_init),
521 value,
522 };
523 let data_type = DataType::new("StubCustomData", metadata, identifier);
524 CustomData::new(Arc::new(inner), data_type)
525}