rust_order_book/
builder.rs1use crate::{
15 journal::{JournalLog, Snapshot},
16 OrderBook, OrderBookOptions,
17};
18
19pub struct OrderBookBuilder {
24 symbol: String,
25 options: OrderBookOptions,
26}
27
28impl OrderBookBuilder {
29 pub fn new(symbol: impl Into<String>) -> Self {
34 Self { symbol: symbol.into(), options: OrderBookOptions::default() }
35 }
36
37 pub fn with_options(mut self, options: OrderBookOptions) -> Self {
41 self.options = options;
42 self
43 }
44
45 pub fn with_snapshot(mut self, snapshot: Snapshot) -> Self {
56 self.options.snapshot = Some(snapshot);
57 self
58 }
59
60 pub fn with_replay_logs(mut self, logs: Vec<JournalLog>) -> Self {
72 self.options.replay_logs = Some(logs);
73 self
74 }
75
76 pub fn with_journaling(mut self, enabled: bool) -> Self {
81 self.options.journaling = enabled;
82 self
83 }
84
85 pub fn build(self) -> OrderBook {
90 let mut ob = OrderBook::new(self.symbol.as_str(), self.options.clone());
91 if let Some(snapshot) = &self.options.snapshot {
92 ob.restore_snapshot(snapshot.clone());
93 }
94
95 if let Some(logs) = self.options.replay_logs {
96 ob.replay_logs(logs).unwrap(); }
98
99 ob
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use std::collections::{BTreeMap, HashMap};
106
107 use crate::{
108 enums::{JournalOp, OrderOptions},
109 order::{OrderId, Price, Quantity},
110 utils::current_timestamp_millis,
111 LimitOrderOptions, MarketOrderOptions, Side,
112 };
113
114 use super::*;
115
116 #[test]
117 fn test_builder_with_defaults() {
118 let ob = OrderBookBuilder::new("BTCUSD").build();
119 assert_eq!(ob.symbol(), "BTCUSD");
120 assert!(!ob.journaling);
121 }
122
123 #[test]
124 fn test_builder_with_journaling_enabled() {
125 let ob = OrderBookBuilder::new("ETHUSD").with_journaling(true).build();
126
127 assert_eq!(ob.symbol(), "ETHUSD");
128 assert!(ob.journaling);
129 }
130
131 #[test]
132 fn test_builder_with_options_struct() {
133 let opts = OrderBookOptions { journaling: true, ..Default::default() };
134
135 let ob = OrderBookBuilder::new("DOGEUSD").with_options(opts.clone()).build();
136
137 assert_eq!(ob.symbol(), "DOGEUSD");
138 assert_eq!(ob.journaling, opts.journaling);
139 }
140
141 #[test]
142 fn test_builder_with_snapshot() {
143 let snap = Snapshot {
145 orders: HashMap::new(),
146 bids: BTreeMap::new(),
147 asks: BTreeMap::new(),
148 last_op: 42,
149 next_order_id: OrderId(100),
150 ts: current_timestamp_millis(),
151 };
152
153 let book = OrderBookBuilder::new("BTCUSD").with_snapshot(snap).build();
154
155 assert_eq!(book.last_op, 42);
156 assert_eq!(book.next_order_id, OrderId(100));
157 assert_eq!(book.orders.len(), 0);
158 }
159
160 #[test]
161 fn test_builder_with_replay_logs() {
162 let logs = vec![
164 JournalLog {
165 op_id: 1,
166 ts: 123457,
167 op: JournalOp::Limit,
168 o: OrderOptions::Limit(LimitOrderOptions {
169 quantity: Quantity(10),
170 price: Price(1100),
171 side: Side::Sell,
172 post_only: None,
173 time_in_force: None,
174 }),
175 },
176 JournalLog {
177 op_id: 2,
178 ts: 123457,
179 op: JournalOp::Limit,
180 o: OrderOptions::Limit(LimitOrderOptions {
181 quantity: Quantity(10),
182 price: Price(1000),
183 side: Side::Buy,
184 post_only: None,
185 time_in_force: None,
186 }),
187 },
188 JournalLog {
189 op_id: 3,
190 ts: 123456,
191 op: JournalOp::Market,
192 o: OrderOptions::Market(MarketOrderOptions {
193 quantity: Quantity(5),
194 side: Side::Buy,
195 }),
196 },
197 ];
198
199 let ob = OrderBookBuilder::new("BTCUSD").with_replay_logs(logs.clone()).build();
201
202 assert_eq!(ob.orders.len(), 2);
204
205 assert_eq!(ob.get_order(OrderId(0)).unwrap().remaining_qty(), Quantity(5));
207 assert_eq!(ob.get_order(OrderId(1)).unwrap().remaining_qty(), Quantity(10));
208 }
209}