catalog_manager/
symbols.rs1use chrono::Datelike;
2
3pub fn format_symbol(underlying: &str, expiry_code: u32, strike: f64, is_call: bool) -> String {
4 let strike_str = if strike.fract().abs() < 1e-6 {
5 format!("{:.0}", strike.round())
6 } else {
7 let mut formatted = format!("{:.8}", strike);
8 while formatted.contains('.') && formatted.ends_with('0') {
9 formatted.pop();
10 }
11 if formatted.ends_with('.') {
12 formatted.pop();
13 }
14 formatted
15 };
16
17 let option_type = if is_call { "C" } else { "P" };
18 format!(
19 "{}-{:08}-{}-{}",
20 underlying, expiry_code, strike_str, option_type
21 )
22}
23
24pub fn timestamp_to_code(timestamp: i64) -> Option<u32> {
25 let datetime = chrono::DateTime::from_timestamp(timestamp, 0)?;
26 let date = datetime.date_naive();
27 Some((date.year() as u32) * 10000 + date.month() * 100 + date.day())
28}
29
30#[cfg(test)]
31mod tests {
32 use super::*;
33
34 #[test]
35 fn formats_integer_and_decimal_symbols() {
36 assert_eq!(
37 format_symbol("BTC", 20260110, 95_000.0, true),
38 "BTC-20260110-95000-C"
39 );
40 assert_eq!(
41 format_symbol("ETH", 20260110, 3_500.5, false),
42 "ETH-20260110-3500.5-P"
43 );
44 }
45
46 #[test]
47 fn timestamp_to_code_uses_utc_date() {
48 assert_eq!(timestamp_to_code(1_768_003_200), Some(20260110));
49 }
50}