hypercall_settlement/
symbol.rs1use rust_decimal::Decimal;
2use std::str::FromStr;
3
4use crate::{OptionType, SettlementError};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct ParsedSettlementSymbol {
9 pub underlying: String,
10 pub expiry: u64,
11 pub expiry_ts: i64,
12 pub strike: Decimal,
13 pub option_type: OptionType,
14}
15
16impl ParsedSettlementSymbol {
17 pub fn from_symbol(symbol: &str) -> Result<Self, SettlementError> {
22 let parts: Vec<&str> = symbol.split('-').collect();
23 if parts.len() != 4 {
24 return Err(SettlementError::new(format!(
25 "Invalid symbol format: {}",
26 symbol
27 )));
28 }
29
30 let underlying = parts[0].to_string();
31 let expiry = parse_expiry(parts[1])?;
32 let expiry_ts = hypercall_types::expiry_date_to_timestamp_checked(&underlying, expiry)
33 .map_err(|e| SettlementError::new(format!("Invalid expiry in {}: {}", symbol, e)))?
34 as i64;
35 let strike = Decimal::from_str(parts[2]).map_err(|_| {
36 SettlementError::new(format!("Invalid strike in {}: {}", symbol, parts[2]))
37 })?;
38 let option_type = match parts[3] {
39 "C" | "c" => OptionType::Call,
40 "P" | "p" => OptionType::Put,
41 _ => {
42 return Err(SettlementError::new(format!(
43 "Invalid option type in {}: {}",
44 symbol, parts[3]
45 )))
46 }
47 };
48
49 Ok(Self {
50 underlying,
51 expiry,
52 expiry_ts,
53 strike,
54 option_type,
55 })
56 }
57}
58
59fn parse_expiry(expiry: &str) -> Result<u64, SettlementError> {
60 if expiry.chars().all(|c| c.is_ascii_digit()) {
61 return expiry
62 .parse::<u64>()
63 .map_err(|_| SettlementError::new(format!("Invalid expiry: {}", expiry)));
64 }
65
66 parse_deribit_expiry(expiry)
67 .ok_or_else(|| SettlementError::new(format!("Invalid expiry format: {}", expiry)))
68}
69
70fn parse_deribit_expiry(expiry: &str) -> Option<u64> {
71 if !(6..=7).contains(&expiry.len()) {
72 return None;
73 }
74
75 let day_len = expiry.len() - 5;
76 let day = expiry[..day_len].parse::<u32>().ok()?;
77 if !(1..=31).contains(&day) {
78 return None;
79 }
80
81 let month = match &expiry[day_len..day_len + 3] {
82 "JAN" => 1,
83 "FEB" => 2,
84 "MAR" => 3,
85 "APR" => 4,
86 "MAY" => 5,
87 "JUN" => 6,
88 "JUL" => 7,
89 "AUG" => 8,
90 "SEP" => 9,
91 "OCT" => 10,
92 "NOV" => 11,
93 "DEC" => 12,
94 _ => return None,
95 };
96 let year = expiry[day_len + 3..].parse::<u32>().ok()?;
97 Some((2000 + year) as u64 * 10000 + month as u64 * 100 + day as u64)
98}
99
100#[derive(Debug, Clone, PartialEq, Eq)]
102pub struct SettlementInstrument {
103 pub symbol: String,
104 pub underlying: String,
105 pub expiry: u64,
106 pub expiry_ts: i64,
107 pub strike: Decimal,
108 pub option_type: OptionType,
109}
110
111impl SettlementInstrument {
112 pub fn from_symbol(symbol: &str) -> Result<Self, SettlementError> {
113 let parsed = ParsedSettlementSymbol::from_symbol(symbol)?;
114 Ok(Self {
115 symbol: symbol.to_string(),
116 underlying: parsed.underlying,
117 expiry: parsed.expiry,
118 expiry_ts: parsed.expiry_ts,
119 strike: parsed.strike,
120 option_type: parsed.option_type,
121 })
122 }
123}