1use anyhow::{Result, anyhow, bail};
2
3use crate::types::{QotMarket, Security};
4
5const SUPPORTED_SYMBOL_MARKETS: &str =
6 "HK, HK_FUTURE, US, SH, SZ, SG, JP, AU, MY, CA, FX, CRYPTO, CC";
7pub const MAX_SYMBOL_LEN: usize = 160;
15pub const MAX_SYMBOL_CODE_LEN: usize = 128;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct ParsedSymbol {
19 pub market: QotMarket,
20 pub code: String,
21}
22
23impl ParsedSymbol {
24 pub fn market_i32(&self) -> i32 {
25 self.market as i32
26 }
27
28 pub fn into_security(self) -> Security {
29 Security::new(self.market, self.code)
30 }
31}
32
33pub fn parse_symbol_parts(s: &str) -> Result<ParsedSymbol> {
34 validate_full_symbol_len(s)?;
35 let (market_str, code) = s.split_once('.').ok_or_else(|| {
36 anyhow!("invalid symbol {s:?}: expected MARKET.CODE (e.g. HK.00700, US.AAPL, SH.600519)")
37 })?;
38 if code.is_empty() {
39 bail!("invalid symbol {s:?}: code part is empty");
40 }
41 validate_symbol_code_len(code)?;
42
43 let market = match market_str.to_ascii_uppercase().as_str() {
44 "HK" => QotMarket::HkSecurity,
45 "HK_FUTURE" => QotMarket::HkFuture,
46 "US" => QotMarket::UsSecurity,
47 "SH" => QotMarket::CnshSecurity,
48 "SZ" => QotMarket::CnszSecurity,
49 "SG" => QotMarket::SgSecurity,
50 "JP" => QotMarket::JpSecurity,
51 "AU" => QotMarket::AuSecurity,
52 "MY" => QotMarket::MySecurity,
53 "CA" => QotMarket::CaSecurity,
54 "FX" => QotMarket::FxSecurity,
55 "CRYPTO" | "CC" => QotMarket::Crypto,
56 other => bail!(
57 "invalid symbol {s:?}: unknown market {other:?} (supported: {SUPPORTED_SYMBOL_MARKETS})"
58 ),
59 };
60
61 Ok(ParsedSymbol {
62 market,
63 code: code.to_string(),
64 })
65}
66
67pub fn validate_full_symbol_len(s: &str) -> Result<()> {
68 if s.len() > MAX_SYMBOL_LEN {
69 bail!(
70 "invalid symbol {s:?}: symbol length {} exceeds max {MAX_SYMBOL_LEN}",
71 s.len()
72 );
73 }
74 Ok(())
75}
76
77pub fn validate_symbol_code_len(code: &str) -> Result<()> {
78 if code.len() > MAX_SYMBOL_CODE_LEN {
79 bail!(
80 "invalid symbol code: code length {} exceeds max {MAX_SYMBOL_CODE_LEN}",
81 code.len()
82 );
83 }
84 Ok(())
85}
86
87pub fn parse_symbol(s: &str) -> Result<Security> {
88 Ok(parse_symbol_parts(s)?.into_security())
89}
90
91pub fn format_symbol(sec: &Security) -> String {
92 let market = match sec.market {
93 QotMarket::HkSecurity => "HK",
94 QotMarket::HkFuture => "HK_FUTURE",
95 QotMarket::UsSecurity => "US",
96 QotMarket::CnshSecurity => "SH",
97 QotMarket::CnszSecurity => "SZ",
98 QotMarket::SgSecurity => "SG",
99 QotMarket::JpSecurity => "JP",
100 QotMarket::AuSecurity => "AU",
101 QotMarket::MySecurity => "MY",
102 QotMarket::CaSecurity => "CA",
103 QotMarket::FxSecurity => "FX",
104 QotMarket::Crypto => "CC",
105 QotMarket::Unknown => "UNKNOWN",
106 };
107 format!("{market}.{}", sec.code)
108}
109
110#[cfg(test)]
111mod tests;