use anyhow::{anyhow, Result};
use async_trait::async_trait;
use crate::types::{Stock, StockApi};
pub const COMMON_SZ: &str = "SZ";
pub const COMMON_SH: &str = "SH";
pub const COMMON_HK: &str = "HK";
pub const COMMON_US: &str = "US";
#[derive(Clone, Copy)]
pub struct BaseApi;
#[async_trait]
impl StockApi for BaseApi {
async fn get_stock(&self, _code: &str) -> Result<Stock> {
Err(anyhow!("未实现获取股票数据"))
}
async fn get_stocks(&self, _codes: &[String]) -> Result<Vec<Stock>> {
Err(anyhow!("未实现获取股票数据组"))
}
async fn search_stocks(&self, _key: &str) -> Result<Vec<Stock>> {
Err(anyhow!("未实现搜索股票代码"))
}
}
pub fn dedup_codes(codes: &[String]) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
for code in codes {
if !code.is_empty() && !out.contains(code) {
out.push(code.clone());
}
}
out
}
pub fn code_market(code: &str) -> &str {
if code.len() >= 2 {
&code[..2]
} else {
""
}
}
pub fn parse_num(s: Option<&str>) -> f64 {
s.unwrap_or("0").parse::<f64>().unwrap_or(0.0)
}
pub fn as_f64(v: Option<&serde_json::Value>) -> f64 {
match v {
Some(serde_json::Value::Number(n)) => n.as_f64().unwrap_or(0.0),
Some(serde_json::Value::String(s)) => s.parse::<f64>().unwrap_or(0.0),
_ => 0.0,
}
}
pub fn as_string(v: Option<&serde_json::Value>) -> String {
match v {
Some(serde_json::Value::String(s)) => s.clone(),
Some(other) => other.to_string(),
None => "---".to_string(),
}
}
pub fn percent(now: f64, yesterday: f64) -> f64 {
if now == 0.0 || yesterday == 0.0 {
0.0
} else {
now / yesterday - 1.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dedup_codes() {
let codes = vec![
"SH510500".to_string(),
"".to_string(),
"SH510500".to_string(),
"SZ000001".to_string(),
];
assert_eq!(dedup_codes(&codes), vec!["SH510500", "SZ000001"]);
}
#[test]
fn test_percent() {
let p = percent(7.224, 7.149);
assert!((p - (7.224 / 7.149 - 1.0)).abs() < 1e-12);
assert_eq!(percent(0.0, 7.149), 0.0);
}
}