use tenk::sources::{EastMoneySource, SinaSource, THSSource};
use tenk::{DataClient, KLineType};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.init();
println!("=== tenk ETF Data Example ===\n");
let client = DataClient::new()
.with_fund_source(EastMoneySource::default())
.with_fund_source(THSSource::default())
.with_fund_source(SinaSource::default());
println!("1. Fetching all ETF codes...");
let etfs = client.get_all_etf_codes().await?;
println!(" Found {} ETFs", etfs.len());
let mut sorted_etfs: Vec<_> = etfs.iter()
.filter(|e| e.net_value.is_some())
.collect();
sorted_etfs.sort_by(|a, b| {
b.net_value.unwrap_or(0.0)
.partial_cmp(&a.net_value.unwrap_or(0.0))
.unwrap()
});
println!("\n Top 10 ETFs by NAV:");
println!(" {:10} {:20} {:>10}", "Code", "Name", "NAV");
println!(" {}", "-".repeat(44));
for etf in sorted_etfs.iter().take(10) {
println!(" {:10} {:20} {:>10.3}",
etf.fund_code,
truncate_str(&etf.short_name, 18),
etf.net_value.unwrap_or(0.0));
}
println!();
let popular_etfs = [
("510300", "沪深300ETF"),
("159915", "创业板ETF"),
];
println!("2. Fetching historical data for popular ETFs...");
for (code, name) in &popular_etfs {
match client
.get_etf_market(code, Some("2025-01-01"), Some("2025-01-31"), KLineType::Daily)
.await
{
Ok(data) => {
if !data.is_empty() {
let first = data.first().unwrap();
let last = data.last().unwrap();
let change = (last.close - first.open) / first.open * 100.0;
println!(" {} ({}):", code, name);
println!(" Records: {}, Period change: {:+.2}%", data.len(), change);
}
}
Err(e) => println!(" {} ({}): Error - {}", code, name, e),
}
}
println!();
println!("3. Fetching current ETF prices...");
let etf_codes: Vec<&str> = popular_etfs.iter().map(|(c, _)| *c).collect();
match client.get_etf_current(&etf_codes).await {
Ok(current) => {
println!(" {:10} {:15} {:>10} {:>10}", "Code", "Name", "Price", "Change%");
println!(" {}", "-".repeat(50));
for data in ¤t {
let change_pct = data.change_pct.unwrap_or(0.0);
let arrow = if change_pct >= 0.0 { "↑" } else { "↓" };
println!(" {:10} {:15} {:>10.3} {:>9.2}%{}",
data.fund_code,
truncate_str(&data.short_name, 13),
data.price,
change_pct.abs(),
arrow);
}
}
Err(e) => println!(" Error fetching current prices: {}", e),
}
println!();
println!("4. Fetching minute data for 510300 (沪深300ETF)...");
match client.get_etf_min("510300").await {
Ok(minutes) => {
println!(" Fetched {} minute records", minutes.len());
if !minutes.is_empty() {
if let Some(first) = minutes.first() {
println!(" First: {} - {:.3}",
first.trade_time.format("%H:%M"), first.price);
}
if let Some(last) = minutes.last() {
println!(" Last: {} - {:.3}",
last.trade_time.format("%H:%M"), last.price);
}
}
}
Err(e) => println!(" Error: {}", e),
}
println!("\nDone!");
Ok(())
}
fn truncate_str(s: &str, max_len: usize) -> String {
if s.chars().count() <= max_len {
s.to_string()
} else {
format!("{}...", s.chars().take(max_len - 3).collect::<String>())
}
}