Skip to main content

catalog_manager/
symbols.rs

1use 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}