1use crate::dto::streaming::UnmatchedOrder;
2use rust_decimal::Decimal;
3use std::collections::HashMap;
4
5#[derive(Debug, Clone)]
6pub struct OrderCache {
7 pub market_id: String,
8 pub runners: HashMap<u64, RunnerOrders>,
9 pub last_update: i64,
10}
11
12#[derive(Debug, Clone)]
13pub struct RunnerOrders {
14 pub selection_id: u64,
15 pub handicap: Option<Decimal>,
16 pub orders: HashMap<String, UnmatchedOrder>,
17 pub matched_backs: HashMap<String, Decimal>,
18 pub matched_lays: HashMap<String, Decimal>,
19}
20
21impl OrderCache {
22 pub fn new(market_id: String) -> Self {
23 Self {
24 market_id,
25 runners: HashMap::new(),
26 last_update: 0,
27 }
28 }
29
30 pub fn update_timestamp(&mut self, timestamp: i64) {
31 self.last_update = timestamp;
32 }
33
34 pub fn get_runner(&self, selection_id: u64) -> Option<&RunnerOrders> {
35 self.runners.get(&selection_id)
36 }
37
38 pub fn get_runner_mut(&mut self, selection_id: u64) -> &mut RunnerOrders {
39 self.runners
40 .entry(selection_id)
41 .or_insert_with(|| RunnerOrders::new(selection_id))
42 }
43
44 pub fn get_all_orders(&self) -> Vec<&UnmatchedOrder> {
45 self.runners
46 .values()
47 .flat_map(|r| r.orders.values())
48 .collect()
49 }
50
51 pub fn get_active_orders(&self) -> Vec<&UnmatchedOrder> {
52 self.get_all_orders()
53 .into_iter()
54 .filter(|o| o.status == "E")
55 .collect()
56 }
57
58 pub fn clear(&mut self) {
59 self.runners.clear();
60 }
61}
62
63impl RunnerOrders {
64 pub fn new(selection_id: u64) -> Self {
65 Self {
66 selection_id,
67 handicap: None,
68 orders: HashMap::new(),
69 matched_backs: HashMap::new(),
70 matched_lays: HashMap::new(),
71 }
72 }
73
74 pub fn set_handicap(&mut self, handicap: Option<Decimal>) {
75 self.handicap = handicap;
76 }
77
78 pub fn apply_full_image(&mut self, orders: Vec<UnmatchedOrder>) {
79 self.orders.clear();
80 for order in orders {
81 self.orders.insert(order.id.clone(), order);
82 }
83 }
84
85 pub fn update_order(&mut self, order: UnmatchedOrder) {
86 if order.status == "EC" {
87 self.orders.remove(&order.id);
88 } else {
89 self.orders.insert(order.id.clone(), order);
90 }
91 }
92
93 pub fn update_matched_backs(&mut self, matched_backs: Vec<Vec<Decimal>>) {
94 for entry in matched_backs {
95 if entry.len() >= 2 {
96 let price = entry[0].to_string();
97 let size = entry[1];
98 if size.is_zero() {
99 self.matched_backs.remove(&price);
100 } else {
101 self.matched_backs.insert(price, size);
102 }
103 }
104 }
105 }
106
107 pub fn update_matched_lays(&mut self, matched_lays: Vec<Vec<Decimal>>) {
108 for entry in matched_lays {
109 if entry.len() >= 2 {
110 let price = entry[0].to_string();
111 let size = entry[1];
112 if size.is_zero() {
113 self.matched_lays.remove(&price);
114 } else {
115 self.matched_lays.insert(price, size);
116 }
117 }
118 }
119 }
120
121 pub fn clear_matched_backs(&mut self) {
122 self.matched_backs.clear();
123 }
124
125 pub fn clear_matched_lays(&mut self) {
126 self.matched_lays.clear();
127 }
128
129 pub fn get_order(&self, bet_id: &str) -> Option<&UnmatchedOrder> {
130 self.orders.get(bet_id)
131 }
132
133 pub fn get_total_back_matched(&self) -> Decimal {
134 self.matched_backs.values().sum()
135 }
136
137 pub fn get_total_lay_matched(&self) -> Decimal {
138 self.matched_lays.values().sum()
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use rust_decimal_macros::dec;
146
147 fn create_test_order(id: &str, price: Decimal, size: Decimal, status: &str) -> UnmatchedOrder {
148 UnmatchedOrder {
149 id: id.to_string(),
150 p: price,
151 s: size,
152 bsp: None,
153 side: "B".to_string(),
154 status: status.to_string(),
155 pt: "L".to_string(),
156 ot: "L".to_string(),
157 pd: 1234567890000,
158 md: None,
159 cd: None,
160 ld: None,
161 lsrc: None,
162 avp: None,
163 sm: None,
164 sr: Some(size),
165 sl: None,
166 sc: None,
167 sv: None,
168 rac: None,
169 rc: None,
170 rfo: None,
171 rfs: None,
172 }
173 }
174
175 #[test]
176 fn test_order_cache_new() {
177 let cache = OrderCache::new("1.123456".to_string());
178 assert_eq!(cache.market_id, "1.123456");
179 assert!(cache.runners.is_empty());
180 assert_eq!(cache.last_update, 0);
181 }
182
183 #[test]
184 fn test_runner_orders_new() {
185 let runner = RunnerOrders::new(12345);
186 assert_eq!(runner.selection_id, 12345);
187 assert!(runner.handicap.is_none());
188 assert!(runner.orders.is_empty());
189 assert!(runner.matched_backs.is_empty());
190 assert!(runner.matched_lays.is_empty());
191 }
192
193 #[test]
194 fn test_update_order() {
195 let mut runner = RunnerOrders::new(12345);
196 let order = create_test_order("bet1", dec!(2.0), dec!(10.0), "E");
197
198 runner.update_order(order.clone());
199 assert_eq!(runner.orders.len(), 1);
200 assert!(runner.get_order("bet1").is_some());
201 }
202
203 #[test]
204 fn test_remove_completed_order() {
205 let mut runner = RunnerOrders::new(12345);
206 let order1 = create_test_order("bet1", dec!(2.0), dec!(10.0), "E");
207 let order2 = create_test_order("bet1", dec!(2.0), dec!(10.0), "EC");
208
209 runner.update_order(order1);
210 assert_eq!(runner.orders.len(), 1);
211
212 runner.update_order(order2);
213 assert_eq!(runner.orders.len(), 0);
214 }
215
216 #[test]
217 fn test_apply_full_image() {
218 let mut runner = RunnerOrders::new(12345);
219 let order1 = create_test_order("bet1", dec!(2.0), dec!(10.0), "E");
220 let order2 = create_test_order("bet2", dec!(3.0), dec!(20.0), "E");
221
222 runner.update_order(order1.clone());
223 assert_eq!(runner.orders.len(), 1);
224
225 runner.apply_full_image(vec![order1, order2]);
226 assert_eq!(runner.orders.len(), 2);
227 }
228
229 #[test]
230 fn test_update_matched_backs() {
231 let mut runner = RunnerOrders::new(12345);
232
233 runner.update_matched_backs(vec![vec![dec!(2.0), dec!(50.0)], vec![dec!(2.5), dec!(100.0)]]);
234 assert_eq!(runner.matched_backs.len(), 2);
235
236 let keys: Vec<String> = runner.matched_backs.keys().cloned().collect();
237 assert!(keys.contains(&"2".to_string()) || keys.contains(&"2.0".to_string()),
238 "Expected key '2' or '2.0', got: {keys:?}");
239
240 let price_key = dec!(2.0).to_string();
241 assert_eq!(*runner.matched_backs.get(&price_key).unwrap(), dec!(50.0));
242
243 let price_key_2 = dec!(2.5).to_string();
244 assert_eq!(*runner.matched_backs.get(&price_key_2).unwrap(), dec!(100.0));
245 }
246
247 #[test]
248 fn test_remove_matched_backs_with_zero() {
249 let mut runner = RunnerOrders::new(12345);
250
251 runner.update_matched_backs(vec![vec![dec!(2.0), dec!(50.0)], vec![dec!(2.5), dec!(100.0)]]);
252 assert_eq!(runner.matched_backs.len(), 2);
253
254 runner.update_matched_backs(vec![vec![dec!(2.0), Decimal::ZERO]]);
255 assert_eq!(runner.matched_backs.len(), 1);
256 assert!(!runner.matched_backs.contains_key("2"));
257 }
258
259 #[test]
260 fn test_get_all_orders() {
261 let mut cache = OrderCache::new("1.123456".to_string());
262 let order1 = create_test_order("bet1", dec!(2.0), dec!(10.0), "E");
263 let order2 = create_test_order("bet2", dec!(3.0), dec!(20.0), "E");
264
265 cache.get_runner_mut(12345).update_order(order1);
266 cache.get_runner_mut(12346).update_order(order2);
267
268 let all_orders = cache.get_all_orders();
269 assert_eq!(all_orders.len(), 2);
270 }
271
272 #[test]
273 fn test_get_active_orders() {
274 let mut cache = OrderCache::new("1.123456".to_string());
275 let order1 = create_test_order("bet1", dec!(2.0), dec!(10.0), "E");
276 let order2 = create_test_order("bet2", dec!(3.0), dec!(20.0), "EC");
277
278 cache.get_runner_mut(12345).update_order(order1);
279 cache.get_runner_mut(12345).update_order(order2);
280
281 let active_orders = cache.get_active_orders();
282 assert_eq!(active_orders.len(), 1);
283 assert_eq!(active_orders[0].id, "bet1");
284 }
285
286 #[test]
287 fn test_get_total_matched() {
288 let mut runner = RunnerOrders::new(12345);
289
290 runner.update_matched_backs(vec![vec![dec!(2.0), dec!(50.0)], vec![dec!(2.5), dec!(100.0)]]);
291 runner.update_matched_lays(vec![vec![dec!(3.0), dec!(75.0)]]);
292
293 assert_eq!(runner.get_total_back_matched(), dec!(150.0));
294 assert_eq!(runner.get_total_lay_matched(), dec!(75.0));
295 }
296
297 #[test]
298 fn test_clear_cache() {
299 let mut cache = OrderCache::new("1.123456".to_string());
300 let order = create_test_order("bet1", dec!(2.0), dec!(10.0), "E");
301
302 cache.get_runner_mut(12345).update_order(order);
303 assert_eq!(cache.runners.len(), 1);
304
305 cache.clear();
306 assert_eq!(cache.runners.len(), 0);
307 }
308}