use digdigdig3::core::types::SymbolInfo;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SymbolStatus {
Trading,
Halted,
PreLaunch,
Closed,
Unknown,
}
pub fn canonical_status(sym: &SymbolInfo) -> SymbolStatus {
let s = sym.status.trim().to_ascii_lowercase();
if s.is_empty() {
return SymbolStatus::Unknown;
}
if s == "1" {
return SymbolStatus::Trading;
}
if s == "0" {
return SymbolStatus::Closed;
}
if s == "a" {
return SymbolStatus::Trading;
}
if s == "s" {
return SymbolStatus::Halted;
}
const PRELAUNCH: &[&str] = &["prelaunch", "pre-launch", "pretrading", "pre_trading", "preopen", "pre-open", "pre_open"];
const HALTED: &[&str] = &[
"suspend", "suspended", "halt", "halted", "break", "cancel-only", "cancelonly",
"post-only", "postonly", "post_only", "limit-only", "limitonly", "limit_only",
"reduce-only", "reduceonly", "reduce_only", "paused", "pause", "caution", "untradable",
"not_available_for_trading", "not-available-for-trading",
];
const CLOSED: &[&str] = &[
"delist", "delisted", "delisting", "closed", "close", "settled", "settle",
"expired", "expire", "inactive", "offline", "disabled", "disable", "unlisted", "end_of_day",
];
const TRADING: &[&str] = &[
"trading", "online", "live", "tradable", "active", "open", "normal", "enabled",
"enabletrading", "security_trading_status_normal_trading",
];
if PRELAUNCH.iter().any(|t| s.contains(t)) {
SymbolStatus::PreLaunch
} else if HALTED.iter().any(|t| s.contains(t)) {
SymbolStatus::Halted
} else if CLOSED.iter().any(|t| s.contains(t)) {
SymbolStatus::Closed
} else if TRADING.iter().any(|t| s == *t || s.contains(t)) {
SymbolStatus::Trading
} else {
SymbolStatus::Unknown
}
}
pub fn active_only(symbols: Vec<SymbolInfo>) -> Vec<SymbolInfo> {
symbols
.into_iter()
.filter(|s| canonical_status(s) == SymbolStatus::Trading)
.collect()
}
pub fn is_active(sym: &SymbolInfo) -> bool {
canonical_status(sym) == SymbolStatus::Trading
}
#[cfg(test)]
mod tests {
use super::*;
use digdigdig3::core::types::SymbolInfo;
fn sym(status: &str) -> SymbolInfo {
SymbolInfo { status: status.to_string(), ..Default::default() }
}
#[test]
fn maps_native_statuses() {
for t in ["TRADING", "online", "live", "tradable", "active", "open", "Normal", "1", "Trading"] {
assert_eq!(canonical_status(&sym(t)), SymbolStatus::Trading, "{t}");
}
for t in ["suspend", "PostOnly", "cancel-only", "CAUTION", "untradable", "SECURITY_TRADING_STATUS_NOT_AVAILABLE_FOR_TRADING"] {
assert_eq!(canonical_status(&sym(t)), SymbolStatus::Halted, "{t}");
}
for t in ["PreLaunch", "preopen", "PreTrading"] {
assert_eq!(canonical_status(&sym(t)), SymbolStatus::PreLaunch, "{t}");
}
for t in ["delisted", "delisting", "closed", "Settled", "expired", "offline", "Disabled", "0"] {
assert_eq!(canonical_status(&sym(t)), SymbolStatus::Closed, "{t}");
}
assert_eq!(canonical_status(&sym("")), SymbolStatus::Unknown);
assert_eq!(canonical_status(&sym("wat")), SymbolStatus::Unknown);
}
#[test]
fn active_only_filters() {
let v = vec![sym("Trading"), sym("delisted"), sym("PreLaunch"), sym("online"), sym("")];
let out = active_only(v);
assert_eq!(out.len(), 2); assert!(out.iter().all(|s| is_active(s)));
}
}