| Crates.io | borsa-yfinance |
| lib.rs | borsa-yfinance |
| version | 0.2.0 |
| created_at | 2025-10-18 19:35:52.909568+00 |
| updated_at | 2025-10-23 14:29:37.800853+00 |
| description | Yahoo Finance API connector for the borsa financial data ecosystem with comprehensive market data and streaming support. |
| homepage | https://github.com/borsaorg/borsa |
| repository | https://github.com/borsaorg/borsa |
| max_upload_size | |
| id | 1889564 |
| size | 210,605 |
Yahoo Finance connector for the borsa ecosystem. This crate is both a ready-to-use provider and a reference implementation for building custom connectors.
borsa-yfinance implements borsa-core::BorsaConnector using yfinance-rs under the hood. It covers a wide set of capabilities: quotes, history, search, profile, fundamentals, options, analysis, holders, sustainability, and news, and can be used as a reference when building a connector.
Use it directly, or follow its patterns to build your own connector.
[dependencies]
borsa-yfinance = "0.2.0"
borsa-core = "0.2.0"
use borsa_yfinance::YfConnector;
use borsa_core::{connector::QuoteProvider, AssetKind, Instrument};
use std::sync::Arc;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let yf = Arc::new(YfConnector::new_default());
let aapl = Instrument::from_symbol("AAPL", AssetKind::Equity)?;
let q = yf.quote(&aapl).await?;
if let Some(price) = &q.price {
println!("{} price: {}", q.symbol.as_str(), price.format());
}
Ok(())
}
Enable the tracing feature to emit spans for all public provider endpoints (quotes, history, search, profile, fundamentals, options, analysis, holders, ESG, news, streaming):
[dependencies]
borsa-yfinance = { version = "0.2", features = ["tracing"] }
Run with the example subscriber setup:
RUST_LOG=info,borsa=trace,borsa_yfinance=trace \
cargo run -p borsa-examples --example 00_tracing \
--features "borsa/tracing borsa-yfinance/tracing"
use borsa::{Borsa};
use borsa_yfinance::YfConnector;
use borsa_core::{connector::QuoteProvider, AssetKind, Currency, Instrument, Money, Symbol};
use std::sync::Arc;
let yf = Arc::new(YfConnector::new_default());
let borsa = Borsa::builder().with_connector(yf).build()?;
let inst = Instrument::from_symbol("MSFT", AssetKind::Equity)?;
let quote = borsa.quote(&inst).await?;
#[async_trait]
pub trait YfQuotes { async fn fetch(&self, symbols: &[String]) -> Result<Vec<yf::core::Quote>, BorsaError>; }
#[async_trait]
pub trait YfHistory { async fn fetch_full(&self, symbol: &str, req: yf::core::services::HistoryRequest) -> Result<yf::HistoryResponse, BorsaError>; }
// ... YfSearch, YfProfile, YfFundamentals, YfOptions, YfAnalysis, YfHolders, YfEsg, YfNews
#[derive(Clone)]
pub struct RealAdapter { client: yf::YfClient }
impl RealAdapter { pub fn new_default() -> Self { Self { client: yf::YfClient::default() } } }
impl dyn YfQuotes { pub fn from_fn<F>(f: F) -> Arc<dyn YfQuotes> where F: Send + Sync + 'static + Fn(Vec<String>) -> Result<Vec<yf::core::Quote>, BorsaError> { /* ... */ } }
Return the native paft types (Symbol, Money, domain enums) directly from adapters.
Delegate capability traits and advertise them via BorsaConnector::as_*_provider.
#[async_trait]
impl QuoteProvider for YfConnector {
async fn quote(&self, instrument: &Instrument) -> Result<Quote, BorsaError> {
// call adapter + normalise errors
}
}
impl BorsaConnector for YfConnector {
fn name(&self) -> &'static str { "borsa-yfinance" }
fn as_quote_provider(&self) -> Option<&dyn QuoteProvider> {
Some(self)
}
// advertise other capabilities similarly
}
This connector advertises and implements the following capabilities:
Native intervals returned by supported_history_intervals:
The orchestrator may resample as needed (e.g., auto-subdaily->daily, weekly).
Errors from yfinance-rs are converted to BorsaError::connector("borsa-yfinance", message) to provide consistent, debuggable failures in multi-provider flows. Missing symbols surface as BorsaError::NotFound{ .. } where relevant via router logic.
Run:
cargo test -p borsa-yfinance | cat
unsupportedFeature flag: the lightweight adapter helpers (
CloneArcAdapters,YfQuotes::from_fn, etc.) are gated behind the optionaltest-adaptersfeature. Enable it inCargo.toml(for example,borsa-yfinance = { version = "x.y", features = ["test-adapters"] }) or on the command line withcargo test --features borsa-yfinance/test-adapters.
The design here intentionally separates the public connector from IO so you can write fast, deterministic tests with zero network.
use std::sync::Arc;
use borsa_yfinance::YfConnector;
use borsa_yfinance::adapter::{CloneArcAdapters, YfQuotes};
use borsa_core::{connector::QuoteProvider, AssetKind, Currency, Instrument, Money, Symbol};
// 1) Create a minimal adapter that exposes only quotes
struct QuotesOnlyAdapter { quotes: Arc<dyn YfQuotes> }
impl CloneArcAdapters for QuotesOnlyAdapter {
fn clone_arc_quotes(&self) -> Arc<dyn YfQuotes> { self.quotes.clone() }
}
#[tokio::test]
async fn quote_smoke_test() {
// 2) Provide a closure-based quote implementation (no network)
let quotes = <dyn YfQuotes>::from_fn(|symbols| {
assert_eq!(symbols, vec!["AAPL".to_string()]);
let price = Money::from_canonical_str(
"190.0",
Currency::Iso(borsa_core::IsoCurrency::USD),
)
.unwrap();
let previous = Money::from_canonical_str(
"189.5",
Currency::Iso(borsa_core::IsoCurrency::USD),
)
.unwrap();
Ok(vec![yfinance_rs::core::Quote {
symbol: Symbol::new("AAPL").unwrap(),
shortname: Some("Apple".into()),
price: Some(price),
previous_close: Some(previous),
exchange: None,
market_state: None,
}])
});
// 3) Build the connector from the adapter
let yf = YfConnector::from_adapter(QuotesOnlyAdapter { quotes });
// 4) Exercise the API
let aapl = Instrument::from_symbol("AAPL", AssetKind::Equity).unwrap();
let q = yf.quote(&aapl).await.unwrap();
assert_eq!(q.symbol.as_str(), "AAPL");
assert_eq!(q.price.unwrap().format(), "190.0 USD");
}
use std::sync::Arc;
use borsa_yfinance::YfConnector;
use borsa_yfinance::adapter::{CloneArcAdapters, YfSearch};
use borsa_core::{AssetKind, SearchRequest, SearchResponse, SearchResult, Symbol};
struct SearchOnlyAdapter { search: Arc<dyn YfSearch> }
impl CloneArcAdapters for SearchOnlyAdapter {
fn clone_arc_search(&self) -> Arc<dyn YfSearch> { self.search.clone() }
}
#[tokio::test]
async fn search_returns_symbols() {
let search = <dyn YfSearch>::from_fn(|query| {
assert_eq!(query, "Apple");
Ok(SearchResponse {
results: vec![
SearchResult {
symbol: Symbol::new("AAPL").unwrap(),
name: Some("Apple Inc.".into()),
exchange: None,
kind: AssetKind::Equity,
},
SearchResult {
symbol: Symbol::new("APPL34").unwrap(),
name: Some("Apple BDR".into()),
exchange: None,
kind: AssetKind::Equity,
},
],
})
});
let yf = YfConnector::from_adapter(SearchOnlyAdapter { search });
let res = yf.search(SearchRequest::new("Apple").with_limit(2)).await.unwrap();
assert_eq!(res.results.len(), 2);
assert_eq!(res.results[0].symbol.as_str(), "AAPL");
}
use std::sync::Arc;
use borsa::Borsa;
use borsa_core::{Instrument, AssetKind};
use borsa_yfinance::YfConnector;
use borsa_yfinance::adapter::{CloneArcAdapters, YfQuotes};
struct QuotesOnlyAdapter { quotes: Arc<dyn YfQuotes> }
impl CloneArcAdapters for QuotesOnlyAdapter {
fn clone_arc_quotes(&self) -> Arc<dyn YfQuotes> { self.quotes.clone() }
}
#[tokio::test]
async fn router_uses_injected_yf() {
let quotes = <dyn YfQuotes>::from_fn(|symbols| Ok(vec![yfinance_rs::core::Quote {
symbol: symbols[0].clone(),
shortname: None,
regular_market_price: Some(123.45),
regular_market_previous_close: None,
currency: None,
exchange: None,
market_state: None,
}]))
;
let yf = Arc::new(YfConnector::from_adapter(QuotesOnlyAdapter { quotes }));
let borsa = Borsa::builder().with_connector(yf).build()?;
let inst = Instrument::new("MSFT", AssetKind::Equity);
let q = borsa.quote(&inst).await.unwrap();
assert_eq!(q.symbol, "MSFT");
}
Notes:
unsupported defaults.convert.rs pure so they can be fuzzed and unit-tested in isolation.HistoryRequest::from_range(range, interval) and provide a synthetic yfinance_rs::HistoryResponse through YfHistory::from_fn.MIT — see LICENSE
This crate provides access to Yahoo Finance data. Ensure compliance with Yahoo’s terms of service.