use chrono::{Duration, Utc};
use std::fmt::Display;
use std::time::Duration as StdDuration;
use yfinance_rs::core::Interval;
use yfinance_rs::{
DownloadBuilder, NewsTab, StreamBuilder, StreamMethod, Ticker, YfClient, YfClientBuilder,
YfError,
};
fn display_opt<T: Display>(value: Option<&T>) -> String {
value.map_or_else(|| "N/A".to_string(), ToString::to_string)
}
#[tokio::main]
async fn main() -> Result<(), YfError> {
let client = YfClientBuilder::default()
.timeout(StdDuration::from_secs(5))
.build()?;
section_info(&client).await?;
section_fast_info(&client).await?;
section_batch_quotes(&client).await?;
section_download(&client).await?;
section_options(&client).await?;
section_stream(&client).await?;
section_news(&client).await?;
Ok(())
}
async fn section_info(client: &YfClient) -> Result<(), YfError> {
let msft = Ticker::new(client, "MSFT");
let info = msft.info().await?;
println!("--- Ticker Info for {} ---", info.snapshot.instrument);
println!("Name: {}", info.snapshot.name.unwrap_or_default());
println!("Last Price: {}", display_opt(info.snapshot.last.as_ref()));
if let Some(v) = info.snapshot.volume {
println!("Volume (day): {v}");
}
if let Some(pt) = info.price_target.as_ref()
&& let Some(mean) = pt.mean.as_ref()
{
println!("Price target mean: {mean}");
}
if let Some(rs) = info.recommendation_summary.as_ref() {
let mean = rs.mean.unwrap_or_default();
let text = rs.mean_rating_text.as_deref().unwrap_or("N/A");
println!("Recommendation mean: {mean:.2} ({text})");
}
println!();
Ok(())
}
async fn section_fast_info(client: &YfClient) -> Result<(), YfError> {
println!("--- Fast Info for NVDA ---");
let nvda = Ticker::new(client, "NVDA");
let fast_info = nvda.fast_info().await?;
let price_money = fast_info
.snapshot
.last
.clone()
.or_else(|| fast_info.snapshot.previous_close.clone())
.expect("last or previous_close present");
println!(
"{} is trading at {} in {}",
fast_info.snapshot.instrument,
price_money,
fast_info
.snapshot
.instrument
.exchange
.as_ref()
.map(std::string::ToString::to_string)
.unwrap_or_default()
);
if let Some(v) = fast_info.snapshot.volume {
println!("Day volume: {v}");
}
println!();
Ok(())
}
async fn section_batch_quotes(client: &YfClient) -> Result<(), YfError> {
println!("--- Batch Quotes for Multiple Symbols ---");
let quotes = yfinance_rs::quotes(client, vec!["AMD", "INTC", "QCOM"]).await?;
for quote in quotes {
let vol = quote
.day_volume
.map(|v| format!(" (vol: {v})"))
.unwrap_or_default();
println!(
" {}: {}{}",
quote.instrument,
display_opt(quote.price.as_ref()),
vol
);
}
println!();
Ok(())
}
async fn section_download(client: &YfClient) -> Result<(), YfError> {
let symbols = vec!["AAPL", "GOOG", "TSLA"];
let today = Utc::now();
let three_months_ago = today - Duration::days(90);
println!("--- Historical Data for Multiple Symbols ---");
let results = DownloadBuilder::new(client)
.symbols(symbols)
.between(three_months_ago, today)
.interval(Interval::D1)
.run()
.await?;
for entry in &results.entries {
let symbol = entry.instrument.symbol.as_str();
let candles = &entry.history.candles;
println!("{symbol} has {} data points.", candles.len());
if let Some(last_candle) = candles.last() {
println!(" Last close price: {}", last_candle.ohlc.close);
}
}
println!();
Ok(())
}
async fn section_options(client: &YfClient) -> Result<(), YfError> {
let aapl = Ticker::new(client, "AAPL");
let expirations = aapl.options().await?;
if let Some(first_expiry) = expirations.first() {
println!("--- Options Chain for AAPL ({first_expiry}) ---");
let chain = aapl.option_chain(Some(*first_expiry)).await?;
println!(
" Found {} calls and {} puts.",
chain.calls().count(),
chain.puts().count()
);
if let Some(first_call) = chain.calls().next() {
let contract = first_call
.key
.contract_instrument
.as_ref()
.unwrap_or(&first_call.key.underlying);
println!(
" First call option: {} @ {}",
contract, first_call.key.strike
);
}
}
println!();
Ok(())
}
async fn section_stream(client: &YfClient) -> Result<(), YfError> {
println!("--- Streaming Real-time Quotes for MSFT and GOOG ---");
println!("(Streaming for 10 seconds or until stopped...)");
let (handle, mut receiver) = StreamBuilder::new(client)
.symbols(vec!["MSFT", "GOOG"])
.method(StreamMethod::WebsocketWithFallback)
.start()
.await?;
let stream_task = tokio::spawn(async move {
let mut count = 0;
while let Some(update) = receiver.recv().await {
let vol = update
.volume
.map(|v| format!(" (volume: {v})"))
.unwrap_or_default();
println!(
"[{}] {} @ {}{}",
update.ts,
update.instrument,
display_opt(update.price.as_ref()),
vol
);
count += 1;
if count >= 1000 {
break;
}
}
println!("Finished streaming after {count} updates.");
});
tokio::select! {
() = tokio::time::sleep(StdDuration::from_secs(10)) => {
println!("Stopping stream due to timeout.");
handle.stop().await;
}
_ = stream_task => {
println!("Stream task completed on its own.");
}
};
Ok(())
}
async fn section_news(client: &YfClient) -> Result<(), YfError> {
let tesla_news = Ticker::new(client, "TSLA");
let articles = tesla_news
.news_builder()
.tab(NewsTab::PressReleases)
.count(5)
.fetch()
.await?;
println!("\n--- Latest 5 Press Releases for TSLA ---");
for article in articles {
println!(
"- {} by {}",
article.title,
article.publisher.unwrap_or_else(|| "Unknown".to_string())
);
}
Ok(())
}