added corporate quarterly announcments for the last 4 years
This commit is contained in:
@@ -2,8 +2,10 @@
|
||||
use super::types::{CompanyEvent, CompanyPrice};
|
||||
use fantoccini::{Client, Locator};
|
||||
use scraper::{Html, Selector};
|
||||
use chrono::{NaiveDate, Datelike};
|
||||
use chrono::{NaiveDate};
|
||||
use tokio::time::{sleep, Duration};
|
||||
use yfinance_rs::{YfClient, Ticker, Range, Interval};
|
||||
use yfinance_rs::core::conversions::money_to_f64;
|
||||
|
||||
const USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36";
|
||||
|
||||
@@ -54,9 +56,9 @@ pub async fn fetch_earnings_history(client: &Client, ticker: &str) -> anyhow::Re
|
||||
let cols: Vec<String> = row.select(&Selector::parse("td").unwrap())
|
||||
.map(|td| td.text().collect::<Vec<_>>().join(" ").trim().to_string())
|
||||
.collect();
|
||||
if cols.len() < 6 { continue; }
|
||||
if cols.len() < 4 { continue; }
|
||||
|
||||
let full_date = &cols[2];
|
||||
let full_date = &cols[0];
|
||||
let parts: Vec<&str> = full_date.split(" at ").collect();
|
||||
let raw_date = parts[0].trim();
|
||||
let time_str = if parts.len() > 1 { parts[1].trim() } else { "" };
|
||||
@@ -66,8 +68,8 @@ pub async fn fetch_earnings_history(client: &Client, ticker: &str) -> anyhow::Re
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
let eps_forecast = parse_float(&cols[3]);
|
||||
let eps_actual = if cols[4] == "-" { None } else { parse_float(&cols[4]) };
|
||||
let eps_forecast = parse_float(&cols[1]);
|
||||
let eps_actual = if cols[2] == "-" { None } else { parse_float(&cols[2]) };
|
||||
|
||||
let surprise_pct = if let (Some(f), Some(a)) = (eps_forecast, eps_actual) {
|
||||
if f.abs() > 0.001 { Some((a - f) / f.abs() * 100.0) } else { None }
|
||||
@@ -85,7 +87,7 @@ pub async fn fetch_earnings_history(client: &Client, ticker: &str) -> anyhow::Re
|
||||
ticker: ticker.to_string(),
|
||||
date: date.format("%Y-%m-%d").to_string(),
|
||||
time,
|
||||
period: "".to_string(), // No period info available, set to empty
|
||||
period: "".to_string(),
|
||||
eps_forecast,
|
||||
eps_actual,
|
||||
revenue_forecast: None,
|
||||
@@ -98,38 +100,46 @@ pub async fn fetch_earnings_history(client: &Client, ticker: &str) -> anyhow::Re
|
||||
Ok(events)
|
||||
}
|
||||
|
||||
pub async fn fetch_price_history(client: &Client, ticker: &str, start: &str, end: &str) -> anyhow::Result<Vec<CompanyPrice>> {
|
||||
let start_ts = NaiveDate::parse_from_str(start, "%Y-%m-%d")?
|
||||
.and_hms_opt(0, 0, 0).unwrap().and_utc()
|
||||
.timestamp();
|
||||
pub async fn fetch_price_history(
|
||||
ticker: &str,
|
||||
start: &str,
|
||||
end: &str,
|
||||
) -> anyhow::Result<Vec<CompanyPrice>> {
|
||||
let client = YfClient::default();
|
||||
let tk = Ticker::new(&client, ticker);
|
||||
|
||||
let end_ts = NaiveDate::parse_from_str(end, "%Y-%m-%d")?
|
||||
.succ_opt().unwrap()
|
||||
.and_hms_opt(0, 0, 0).unwrap().and_utc()
|
||||
.timestamp();
|
||||
// We request the maximum range – the library will automatically respect Yahoo's limits
|
||||
let history = tk
|
||||
.history(Some(Range::Max), Some(Interval::D1), true)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("Yahoo Finance API error for {ticker}: {e:?}"))?;
|
||||
|
||||
let url = format!(
|
||||
"https://query1.finance.yahoo.com/v7/finance/download/{ticker}?period1={start_ts}&period2={end_ts}&interval=1d&events=history&includeAdjustedClose=true"
|
||||
);
|
||||
let mut prices = Vec::with_capacity(history.len());
|
||||
|
||||
client.goto(&url).await?;
|
||||
let csv = client.source().await?;
|
||||
for candle in history {
|
||||
let date_str = candle.ts.format("%Y-%m-%d").to_string();
|
||||
|
||||
// Filter by user-defined start / end
|
||||
if date_str < (*start).to_string() || date_str > (*end).to_string() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut prices = Vec::new();
|
||||
for line in csv.lines().skip(1) {
|
||||
let cols: Vec<&str> = line.split(',').collect();
|
||||
if cols.len() < 7 { continue; }
|
||||
prices.push(CompanyPrice {
|
||||
ticker: ticker.to_string(),
|
||||
date: cols[0].to_string(),
|
||||
open: cols[1].parse()?,
|
||||
high: cols[2].parse()?,
|
||||
low: cols[3].parse()?,
|
||||
close: cols[4].parse()?,
|
||||
adj_close: cols[5].parse()?,
|
||||
volume: cols[6].parse()?,
|
||||
date: date_str,
|
||||
open: money_to_f64(&candle.open),
|
||||
high: money_to_f64(&candle.high),
|
||||
low: money_to_f64(&candle.low),
|
||||
// close_unadj is the raw (non-adjusted) close; close is the adjusted one
|
||||
close: money_to_f64(&candle.close_unadj.unwrap_or(candle.close.clone())),
|
||||
adj_close: money_to_f64(&candle.close),
|
||||
volume: candle.volume.unwrap_or(0),
|
||||
});
|
||||
}
|
||||
|
||||
// Sort just in case (normally already sorted)
|
||||
prices.sort_by_key(|p| p.date.clone());
|
||||
|
||||
Ok(prices)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user