use crate::client::AkShareClient;
use crate::error::{Error, Result};
use crate::types::{CandlePoint, QuoteSnapshot};
impl AkShareClient {
pub async fn hk_market_cap_from_tencent(&self, symbol: &str) -> Result<Option<f64>> {
let code = symbol.trim().trim_start_matches('0');
let code = if code.is_empty() { "0" } else { code };
let code = format!("{:0>5}", code);
let url = format!("https://qt.gtimg.cn/q=r_hk{}", code);
let resp = self
.get(&url)
.header("User-Agent", "Mozilla/5.0")
.send()
.await?
.text()
.await?;
if let Some(start) = resp.find('"')
&& let Some(end) = resp.rfind('"')
&& start < end
{
let data = &resp[start + 1..end];
let fields: Vec<&str> = data.split('~').collect();
if fields.len() > 44
&& let Ok(cap_yi) = fields[44].parse::<f64>()
&& cap_yi > 0.0
{
return Ok(Some(cap_yi * 100_000_000.0));
}
}
Ok(None)
}
}
fn hk_yahoo_symbol(symbol: &str) -> Result<String> {
let trimmed = symbol.trim();
let code = if let Some((c, suffix)) = trimmed.split_once('.') {
if suffix.eq_ignore_ascii_case("HK") {
c
} else {
trimmed
}
} else {
trimmed
};
let digits: String = code.trim_start_matches('0').to_string();
let digits_str = if digits.is_empty() { "0" } else { &digits };
if digits_str.len() > 5 || !digits_str.chars().all(|c| c.is_ascii_digit()) {
return Err(Error::invalid_input(format!("invalid HK symbol: {symbol}")));
}
Ok(format!("{digits_str:0>5}.HK"))
}
impl AkShareClient {
pub async fn hk_quote(&self, symbol: &str) -> Result<QuoteSnapshot> {
if let Ok(quote) = self.tencent_hk_quote(symbol).await {
return Ok(quote);
}
let yahoo_symbol = hk_yahoo_symbol(symbol)?;
let mut candles = self.yahoo_candles(&yahoo_symbol, 2).await?;
let last = candles
.pop()
.ok_or_else(|| Error::upstream("no HK quote data"))?;
Ok(QuoteSnapshot {
symbol: symbol.to_uppercase(),
date: last.trade_date,
open: last.open,
high: last.high,
low: last.low,
close: last.close,
volume: last.volume,
})
}
pub async fn hk_candles(&self, symbol: &str, limit: usize) -> Result<Vec<CandlePoint>> {
match self.tencent_hk_candles(symbol, limit).await {
Ok(items) if !items.is_empty() => return Ok(items),
_ => {}
}
let yahoo_symbol = hk_yahoo_symbol(symbol)?;
self.yahoo_candles(&yahoo_symbol, limit).await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hk_yahoo_symbol() {
assert_eq!(hk_yahoo_symbol("00593").unwrap(), "00593.HK");
assert_eq!(hk_yahoo_symbol("593").unwrap(), "00593.HK");
assert_eq!(hk_yahoo_symbol("00593.HK").unwrap(), "00593.HK");
assert_eq!(hk_yahoo_symbol("1").unwrap(), "00001.HK");
assert_eq!(hk_yahoo_symbol("9988").unwrap(), "09988.HK");
assert!(hk_yahoo_symbol("AAPL").is_err());
}
}