1use crate::SmartString;
4use rust_decimal::Decimal;
5use std::str::FromStr;
6#[allow(unused_imports)]
8use wide::f32x8;
9
10#[must_use]
12pub fn parse_decimal_safe(s: &str) -> Decimal {
13 Decimal::from_str(s).unwrap_or(Decimal::ZERO)
14}
15
16#[must_use]
18pub fn parse_decimal_option(s: Option<&str>) -> Option<Decimal> {
19 s.and_then(|val| Decimal::from_str(val).ok())
20}
21
22#[must_use]
24pub fn format_decimal(value: Decimal) -> SmartString {
25 let s = value.to_string();
26 if s.contains('.') {
27 s.trim_end_matches('0').trim_end_matches('.').into()
28 } else {
29 s.into()
30 }
31}
32
33#[must_use]
35pub fn generate_order_id() -> SmartString {
36 uuid::Uuid::new_v4().to_string().into()
37}
38
39#[must_use]
41pub fn normalize_symbol(symbol: &str) -> SmartString {
42 symbol.replace(['-', '_', '/'], "").to_uppercase().into()
43}
44
45#[must_use]
47pub fn parse_symbol_pair(symbol: &str) -> Option<(SmartString, SmartString)> {
48 if let Some(pos) = symbol.find('-') {
50 let (base, quote) = symbol.split_at(pos);
51 return Some((base.into(), quote[1..].into()));
52 }
53
54 if let Some(pos) = symbol.find('_') {
55 let (base, quote) = symbol.split_at(pos);
56 return Some((base.into(), quote[1..].into()));
57 }
58
59 if let Some(pos) = symbol.find('/') {
60 let (base, quote) = symbol.split_at(pos);
61 return Some((base.into(), quote[1..].into()));
62 }
63
64 if let Some(base) = symbol.strip_suffix("USDT") {
66 return Some((base.into(), "USDT".into()));
67 }
68
69 if let Some(base) = symbol.strip_suffix("BTC") {
70 return Some((base.into(), "BTC".into()));
71 }
72
73 if let Some(base) = symbol.strip_suffix("ETH") {
74 return Some((base.into(), "ETH".into()));
75 }
76
77 None
78}
79
80#[must_use]
82pub fn bp_to_decimal(bp: i32) -> Decimal {
83 Decimal::from(bp) / Decimal::from(10000)
84}
85
86#[must_use]
88pub fn decimal_to_bp(value: Decimal) -> i32 {
89 use rust_decimal::prelude::ToPrimitive;
90 (value * Decimal::from(10000)).round().to_i32().unwrap_or(0)
91}
92
93pub mod id_generation {
95 use crate::SmartString;
96 use uuid::Uuid;
97
98 #[inline]
100 #[must_use]
101 pub fn generate_report_id(prefix: &str, suffix: &str) -> SmartString {
102 let mut id = SmartString::new();
103 id.push_str(prefix);
104 id.push('_');
105 id.push_str(suffix);
106 id
107 }
108
109 #[inline]
111 #[must_use]
112 pub fn generate_report_id_with_uuid(prefix: &str) -> SmartString {
113 let uuid_str = Uuid::new_v4().to_string();
114 generate_report_id(prefix, &uuid_str)
115 }
116
117 #[inline]
119 #[must_use]
120 pub fn generate_multi_part_id(parts: &[&str], separator: char) -> SmartString {
121 let mut id = SmartString::new();
122
123 for (i, part) in parts.iter().enumerate() {
124 if i > 0 {
125 id.push(separator);
126 }
127 id.push_str(part);
128 }
129 id
130 }
131
132 #[inline]
134 #[must_use]
135 pub fn generate_ack_id(order_id: &str) -> SmartString {
136 generate_report_id("ack", order_id)
137 }
138
139 #[inline]
141 #[must_use]
142 pub fn generate_rejection_id(order_id: &str) -> SmartString {
143 generate_report_id("rej", order_id)
144 }
145
146 #[inline]
148 #[must_use]
149 pub fn generate_cancel_id(order_id: &str) -> SmartString {
150 generate_report_id("cancel", order_id)
151 }
152
153 #[inline]
155 #[must_use]
156 pub fn generate_fill_id(order_id: &str) -> SmartString {
157 generate_report_id("fill", order_id)
158 }
159
160 #[inline]
162 #[must_use]
163 pub fn generate_modify_id(order_id: &str) -> SmartString {
164 generate_report_id("modify", order_id)
165 }
166
167 #[inline]
169 #[must_use]
170 pub fn generate_exchange_report_id(exchange: &str, suffix: &str) -> SmartString {
171 generate_report_id(exchange, suffix)
172 }
173
174 #[inline]
176 #[must_use]
177 pub fn generate_ws_report_id(operation: &str, suffix: &str) -> SmartString {
178 let mut id = SmartString::new();
179 id.push_str("ws_");
180 id.push_str(operation);
181 id.push('_');
182 id.push_str(suffix);
183 id
184 }
185
186 #[inline]
188 #[must_use]
189 pub fn generate_exchange_order_id(exchange: &str) -> SmartString {
190 let uuid_str = Uuid::new_v4().to_string();
191 generate_report_id(&format!("{exchange}_order"), &uuid_str)
192 }
193
194 #[inline]
196 #[must_use]
197 pub fn generate_batch_id(prefix: &str, index: usize) -> SmartString {
198 let uuid_str = Uuid::new_v4().to_string();
199 let index_str = index.to_string();
200 generate_multi_part_id(&[prefix, "batch", &uuid_str, &index_str], '_')
201 }
202
203 #[inline]
205 #[must_use]
206 pub fn generate_ws_timestamp_id(operation: &str, timestamp_ns: u64) -> SmartString {
207 let timestamp_str = timestamp_ns.to_string();
208 generate_ws_report_id(operation, ×tamp_str)
209 }
210
211 #[inline]
213 #[must_use]
214 pub fn generate_acknowledgment_id(order_id: &str) -> SmartString {
215 generate_report_id("ack", order_id)
216 }
217
218 #[inline]
220 #[must_use]
221 pub fn generate_execution_id(order_id: &str) -> SmartString {
222 generate_report_id("exec", order_id)
223 }
224
225 #[inline]
227 #[must_use]
228 pub fn generate_exchange_request_id(
229 _exchange: &str,
230 operation: &str,
231 counter: u64,
232 ) -> SmartString {
233 let counter_str = counter.to_string();
234 generate_multi_part_id(&[operation, &counter_str], '_')
235 }
236
237 #[inline]
239 #[must_use]
240 pub fn generate_uuid_id() -> SmartString {
241 Uuid::new_v4().to_string().into()
242 }
243
244 #[inline]
246 #[must_use]
247 pub fn generate_client_order_id(suffix: &str) -> SmartString {
248 generate_report_id("order", suffix)
249 }
250
251 #[cfg(test)]
252 mod tests {
253 use super::*;
254
255 #[test]
256 fn test_generate_report_id() {
257 let id = generate_report_id("test", "123");
258 assert_eq!(id, "test_123");
259 }
260
261 #[test]
262 fn test_generate_ack_id() {
263 let id = generate_ack_id("order123");
264 assert_eq!(id, "ack_order123");
265 }
266
267 #[test]
268 fn test_generate_rejection_id() {
269 let id = generate_rejection_id("order456");
270 assert_eq!(id, "rej_order456");
271 }
272
273 #[test]
274 fn test_generate_multi_part_id() {
275 let id = generate_multi_part_id(&["part1", "part2", "part3"], '-');
276 assert_eq!(id, "part1-part2-part3");
277 }
278
279 #[test]
280 fn test_generate_ws_report_id() {
281 let id = generate_ws_report_id("place", "order123");
282 assert_eq!(id, "ws_place_order123");
283 }
284
285 #[test]
286 fn test_generate_exchange_report_id() {
287 let id = generate_exchange_report_id("binance", "execution123");
288 assert_eq!(id, "binance_execution123");
289 }
290
291 #[test]
292 fn test_generate_batch_id() {
293 let id = generate_batch_id("bybit", 0);
294 assert!(id.starts_with("bybit_batch_"));
295 assert!(id.ends_with("_0"));
296 }
297
298 #[test]
299 fn test_generate_ws_timestamp_id() {
300 let id = generate_ws_timestamp_id("error", 1234567890);
301 assert_eq!(id, "ws_error_1234567890");
302 }
303
304 #[test]
305 fn test_generate_acknowledgment_id() {
306 let id = generate_acknowledgment_id("order123");
307 assert_eq!(id, "ack_order123");
308 }
309
310 #[test]
311 fn test_generate_execution_id() {
312 let id = generate_execution_id("order456");
313 assert_eq!(id, "exec_order456");
314 }
315
316 #[test]
317 fn test_generate_exchange_request_id() {
318 let id = generate_exchange_request_id("bybit", "cancel", 42);
319 assert_eq!(id, "cancel_42");
320 }
321
322 #[test]
323 fn test_generate_uuid_id() {
324 let id = generate_uuid_id();
325 assert_eq!(id.len(), 36);
327 assert!(id.contains('-'));
328 }
329
330 #[test]
331 fn test_generate_client_order_id() {
332 let id = generate_client_order_id("123");
333 assert_eq!(id, "order_123");
334 }
335 }
336}
337pub mod simd_nan_safe {
339 use wide::{f32x8, f64x4};
345
346 #[inline(always)]
348 #[must_use]
349 pub fn safe_divide_f64x4(a: f64x4, b: f64x4) -> f64x4 {
350 a / b
352 }
353
354 #[inline(always)]
356 #[must_use]
357 pub fn safe_divide_f32x8(a: f32x8, b: f32x8) -> f32x8 {
358 a / b
359 }
360
361 #[inline(always)]
363 pub fn has_nan_f64x4(v: f64x4) -> bool {
364 v.is_nan().any()
365 }
366
367 #[inline(always)]
369 pub fn has_nan_f32x8(v: f32x8) -> bool {
370 v.is_nan().any()
371 }
372
373 #[inline(always)]
375 #[must_use]
376 pub fn nan_to_zero_f64x4(v: f64x4) -> f64x4 {
377 let mask = !v.is_nan();
378 mask & v
379 }
380
381 #[inline(always)]
383 #[must_use]
384 pub fn nan_to_zero_f32x8(v: f32x8) -> f32x8 {
385 let mask = !v.is_nan();
386 mask & v
387 }
388
389 #[inline(always)]
391 #[must_use]
392 pub fn nan_to_default_f64x4(v: f64x4, default: f64) -> f64x4 {
393 let mask = v.is_nan();
394 let default_vec = f64x4::splat(default);
395 (!mask & v) | (mask & default_vec)
396 }
397
398 #[inline(always)]
400 #[must_use]
401 pub fn nan_to_default_f32x8(v: f32x8, default: f32) -> f32x8 {
402 let mask = v.is_nan();
403 let default_vec = f32x8::splat(default);
404 (!mask & v) | (mask & default_vec)
405 }
406
407 #[inline(always)]
409 #[must_use]
410 pub fn safe_log_f64x4(v: f64x4) -> f64x4 {
411 let arr: [f64; 4] = v.into();
414 f64x4::from([arr[0].ln(), arr[1].ln(), arr[2].ln(), arr[3].ln()])
415 }
416
417 #[inline(always)]
419 #[must_use]
420 pub fn safe_sqrt_f64x4(v: f64x4) -> f64x4 {
421 v.sqrt() }
423
424 #[inline(always)]
426 #[must_use]
427 pub fn clamp_f64x4(v: f64x4, min: f64, max: f64) -> f64x4 {
428 let min_vec = f64x4::splat(min);
429 let max_vec = f64x4::splat(max);
430 v.max(min_vec).min(max_vec) }
432
433 #[inline(always)]
435 #[must_use]
436 pub fn mean_non_nan_f64x4(v: f64x4) -> f64 {
437 let arr: [f64; 4] = v.into();
438 let mut sum = 0.0;
439 let mut count = 0;
440
441 for val in arr.iter() {
442 if !val.is_nan() {
443 sum += val;
444 count += 1;
445 }
446 }
447
448 if count > 0 {
449 sum / count as f64
450 } else {
451 f64::NAN
452 }
453 }
454
455 #[cfg(test)]
456 mod tests {
457 use super::*;
458
459 #[test]
460 fn test_safe_divide() {
461 let a = f64x4::from([10.0, 20.0, 0.0, 30.0]);
462 let b = f64x4::from([2.0, 0.0, 0.0, 5.0]);
463 let result = safe_divide_f64x4(a, b);
464 let arr: [f64; 4] = result.into();
465
466 assert_eq!(arr[0], 5.0);
467 assert!(arr[1].is_infinite()); assert!(arr[2].is_nan()); assert_eq!(arr[3], 6.0);
470 }
471
472 #[test]
473 fn test_nan_to_zero() {
474 let v = f64x4::from([1.0, f64::NAN, 3.0, f64::NAN]);
475 let result = nan_to_zero_f64x4(v);
476 let arr: [f64; 4] = result.into();
477
478 assert_eq!(arr[0], 1.0);
479 assert_eq!(arr[1], 0.0);
480 assert_eq!(arr[2], 3.0);
481 assert_eq!(arr[3], 0.0);
482 }
483
484 #[test]
485 fn test_nan_to_default() {
486 let v = f64x4::from([1.0, f64::NAN, 3.0, f64::NAN]);
487 let result = nan_to_default_f64x4(v, -1.0);
488 let arr: [f64; 4] = result.into();
489
490 assert_eq!(arr[0], 1.0);
491 assert_eq!(arr[1], -1.0);
492 assert_eq!(arr[2], 3.0);
493 assert_eq!(arr[3], -1.0);
494 }
495
496 #[test]
497 fn test_safe_sqrt() {
498 let v = f64x4::from([4.0, -1.0, 16.0, 0.0]);
499 let result = safe_sqrt_f64x4(v);
500 let arr: [f64; 4] = result.into();
501
502 assert_eq!(arr[0], 2.0);
503 assert!(arr[1].is_nan()); assert_eq!(arr[2], 4.0);
505 assert_eq!(arr[3], 0.0);
506 }
507
508 #[test]
509 fn test_mean_non_nan() {
510 let v = f64x4::from([10.0, f64::NAN, 20.0, 30.0]);
511 let result = mean_non_nan_f64x4(v);
512 assert_eq!(result, 20.0); let all_nan = f64x4::from([f64::NAN; 4]);
515 let result_nan = mean_non_nan_f64x4(all_nan);
516 assert!(result_nan.is_nan());
517 }
518 }
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524
525 #[test]
526 fn test_f32x8_available_at_module_level() {
527 use super::simd_nan_safe::*;
530
531 let a = f32x8::from([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]);
533 let b = f32x8::from([2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0, 16.0]);
534
535 let result = safe_divide_f32x8(a, b);
536 let expected = f32x8::from([0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]);
537
538 let result_array: [f32; 8] = result.into();
539 let expected_array: [f32; 8] = expected.into();
540
541 for i in 0..8 {
542 assert!((result_array[i] - expected_array[i]).abs() < f32::EPSILON);
543 }
544 }
545
546 #[test]
547 fn test_smartstring_from_literal_conversions() {
548 let s1: SmartString = "test".into();
550 assert_eq!(s1, "test");
551
552 let s2 = SmartString::from("test");
554 assert_eq!(s2, "test");
555
556 assert_eq!(s1, s2);
558
559 let long: SmartString = "this is a longer string that might use heap allocation".into();
561 assert_eq!(
562 long,
563 "this is a longer string that might use heap allocation"
564 );
565 }
566
567 #[test]
568 fn test_to_string_into_conversions() {
569 #[derive(Debug)]
570 struct TestStruct {
571 value: i32,
572 }
573
574 impl std::fmt::Display for TestStruct {
575 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
576 write!(f, "TestStruct({})", self.value)
577 }
578 }
579
580 let test = TestStruct { value: 42 };
581
582 let s1: SmartString = test.to_string().into();
584 assert_eq!(s1, "TestStruct(42)");
585
586 let s3: SmartString = format!("{test}").into();
591 assert_eq!(s3, "TestStruct(42)");
592 }
593
594 #[test]
595 fn test_parse_symbol_pair() {
596 assert_eq!(
597 parse_symbol_pair("BTC-USDT"),
598 Some(("BTC".into(), "USDT".into()))
599 );
600 assert_eq!(
601 parse_symbol_pair("ETH_BTC"),
602 Some(("ETH".into(), "BTC".into()))
603 );
604 assert_eq!(
605 parse_symbol_pair("BTC/USD"),
606 Some(("BTC".into(), "USD".into()))
607 );
608 assert_eq!(
609 parse_symbol_pair("BTCUSDT"),
610 Some(("BTC".into(), "USDT".into()))
611 );
612 assert_eq!(
613 parse_symbol_pair("ETHBTC"),
614 Some(("ETH".into(), "BTC".into()))
615 );
616 }
617
618 #[test]
619 fn test_format_decimal() {
620 assert_eq!(format_decimal(Decimal::from_str("1.00000").unwrap()), "1");
621 assert_eq!(format_decimal(Decimal::from_str("1.50000").unwrap()), "1.5");
622 assert_eq!(
623 format_decimal(Decimal::from_str("1.12345").unwrap()),
624 "1.12345"
625 );
626 }
627
628 #[test]
629 fn test_overflow_checks_configuration() {
630 let result = 100i64 + 200i64;
638 assert_eq!(result, 300i64);
639
640 let safe_result = i64::MAX.wrapping_add(1);
642 assert_eq!(safe_result, i64::MIN); let financial_calc = Decimal::from_str("999999999999999999.99").unwrap();
646 let result = financial_calc + Decimal::from_str("0.01").unwrap();
647 assert!(result > financial_calc);
648 }
649}