use crate::{
client::FinnhubClient,
error::Result,
models::stock::{BidAsk, CandleResolution, PriceMetrics, Quote, StockCandles, TickData},
};
pub struct PriceEndpoints<'a> {
client: &'a FinnhubClient,
}
impl<'a> PriceEndpoints<'a> {
pub fn new(client: &'a FinnhubClient) -> Self {
Self { client }
}
pub async fn quote(&self, symbol: &str) -> Result<Quote> {
self.client.get(&format!("/quote?symbol={}", symbol)).await
}
pub async fn candles(
&self,
symbol: &str,
resolution: CandleResolution,
from: i64,
to: i64,
) -> Result<StockCandles> {
self.client
.get(&format!(
"/stock/candle?symbol={}&resolution={}&from={}&to={}",
symbol, resolution, from, to
))
.await
}
pub async fn bid_ask(&self, symbol: &str) -> Result<BidAsk> {
self.client
.get(&format!("/stock/bidask?symbol={}", symbol))
.await
}
pub async fn tick_data(
&self,
symbol: &str,
date: &str,
limit: i64,
skip: i64,
) -> Result<TickData> {
self.client
.get(&format!(
"/stock/tick?symbol={}&date={}&limit={}&skip={}",
symbol, date, limit, skip
))
.await
}
pub async fn price_metrics(&self, symbol: &str) -> Result<PriceMetrics> {
self.client
.get(&format!("/stock/price-metric?symbol={}", symbol))
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{ClientConfig, FinnhubClient, RateLimitStrategy};
async fn test_client() -> FinnhubClient {
dotenv::dotenv().ok();
let api_key = std::env::var("FINNHUB_API_KEY").unwrap_or_else(|_| "test_key".to_string());
let mut config = ClientConfig::default();
config.rate_limit_strategy = RateLimitStrategy::FifteenSecondWindow;
FinnhubClient::with_config(api_key, config)
}
#[tokio::test]
#[ignore = "requires API key"]
async fn test_quote() {
let client = test_client().await;
let result = client.stock().quote("AAPL").await;
assert!(result.is_ok(), "Failed to get quote: {:?}", result.err());
let quote = result.unwrap();
assert!(quote.current_price > 0.0);
assert!(quote.high >= quote.low);
}
#[tokio::test]
#[ignore = "requires API key"]
async fn test_candles() {
let client = test_client().await;
let from = chrono::Utc::now().timestamp() - 86400 * 7; let to = chrono::Utc::now().timestamp();
let result = client
.stock()
.candles("AAPL", CandleResolution::Daily, from, to)
.await;
assert!(result.is_ok(), "Failed to get candles: {:?}", result.err());
let candles = result.unwrap();
assert_eq!(candles.status, "ok");
assert!(!candles.close.is_empty());
assert_eq!(candles.close.len(), candles.open.len());
}
#[tokio::test]
#[ignore = "requires API key"]
async fn test_bid_ask() {
let client = test_client().await;
let result = client.stock().bid_ask("AAPL").await;
assert!(result.is_ok(), "Failed to get bid-ask: {:?}", result.err());
}
#[tokio::test]
#[ignore = "requires API key"]
async fn test_tick_data() {
let client = test_client().await;
let date = "2024-01-02";
let limit = 100;
let skip = 0;
let result = client.stock().tick_data("AAPL", date, limit, skip).await;
assert!(
result.is_ok(),
"Failed to get tick data: {:?}",
result.err()
);
}
#[tokio::test]
#[ignore = "requires API key"]
async fn test_price_metrics() {
let client = test_client().await;
let result = client.stock().price_metrics("AAPL").await;
assert!(
result.is_ok(),
"Failed to get price metrics: {:?}",
result.err()
);
}
}