1use digdigdig3::core::types::SymbolInfo;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum SymbolStatus {
18 Trading,
20 Halted,
23 PreLaunch,
25 Closed,
28 Unknown,
30}
31
32pub fn canonical_status(sym: &SymbolInfo) -> SymbolStatus {
37 let s = sym.status.trim().to_ascii_lowercase();
38 if s.is_empty() {
39 return SymbolStatus::Unknown;
40 }
41 if s == "1" {
43 return SymbolStatus::Trading;
44 }
45 if s == "0" {
46 return SymbolStatus::Closed;
47 }
48 if s == "a" {
51 return SymbolStatus::Trading;
52 }
53 if s == "s" {
54 return SymbolStatus::Halted;
55 }
56 const PRELAUNCH: &[&str] = &["prelaunch", "pre-launch", "pretrading", "pre_trading", "preopen", "pre-open", "pre_open"];
59 const HALTED: &[&str] = &[
60 "suspend", "suspended", "halt", "halted", "break", "cancel-only", "cancelonly",
61 "post-only", "postonly", "post_only", "limit-only", "limitonly", "limit_only",
62 "reduce-only", "reduceonly", "reduce_only", "paused", "pause", "caution", "untradable",
63 "not_available_for_trading", "not-available-for-trading",
64 ];
65 const CLOSED: &[&str] = &[
66 "delist", "delisted", "delisting", "closed", "close", "settled", "settle",
67 "expired", "expire", "inactive", "offline", "disabled", "disable", "unlisted", "end_of_day",
68 ];
69 const TRADING: &[&str] = &[
70 "trading", "online", "live", "tradable", "active", "open", "normal", "enabled",
71 "enabletrading", "security_trading_status_normal_trading",
72 ];
73
74 if PRELAUNCH.iter().any(|t| s.contains(t)) {
75 SymbolStatus::PreLaunch
76 } else if HALTED.iter().any(|t| s.contains(t)) {
77 SymbolStatus::Halted
78 } else if CLOSED.iter().any(|t| s.contains(t)) {
79 SymbolStatus::Closed
80 } else if TRADING.iter().any(|t| s == *t || s.contains(t)) {
81 SymbolStatus::Trading
82 } else {
83 SymbolStatus::Unknown
84 }
85}
86
87pub fn active_only(symbols: Vec<SymbolInfo>) -> Vec<SymbolInfo> {
91 symbols
92 .into_iter()
93 .filter(|s| canonical_status(s) == SymbolStatus::Trading)
94 .collect()
95}
96
97pub fn is_active(sym: &SymbolInfo) -> bool {
99 canonical_status(sym) == SymbolStatus::Trading
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use digdigdig3::core::types::SymbolInfo;
106
107 fn sym(status: &str) -> SymbolInfo {
108 SymbolInfo { status: status.to_string(), ..Default::default() }
109 }
110
111 #[test]
112 fn maps_native_statuses() {
113 for t in ["TRADING", "online", "live", "tradable", "active", "open", "Normal", "1", "Trading"] {
115 assert_eq!(canonical_status(&sym(t)), SymbolStatus::Trading, "{t}");
116 }
117 for t in ["suspend", "PostOnly", "cancel-only", "CAUTION", "untradable", "SECURITY_TRADING_STATUS_NOT_AVAILABLE_FOR_TRADING"] {
119 assert_eq!(canonical_status(&sym(t)), SymbolStatus::Halted, "{t}");
120 }
121 for t in ["PreLaunch", "preopen", "PreTrading"] {
123 assert_eq!(canonical_status(&sym(t)), SymbolStatus::PreLaunch, "{t}");
124 }
125 for t in ["delisted", "delisting", "closed", "Settled", "expired", "offline", "Disabled", "0"] {
127 assert_eq!(canonical_status(&sym(t)), SymbolStatus::Closed, "{t}");
128 }
129 assert_eq!(canonical_status(&sym("")), SymbolStatus::Unknown);
131 assert_eq!(canonical_status(&sym("wat")), SymbolStatus::Unknown);
132 }
133
134 #[test]
135 fn active_only_filters() {
136 let v = vec![sym("Trading"), sym("delisted"), sym("PreLaunch"), sym("online"), sym("")];
137 let out = active_only(v);
138 assert_eq!(out.len(), 2); assert!(out.iter().all(|s| is_active(s)));
140 }
141}