mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-11-07 09:45:36 +00:00
feat: login-free Stockbit intraday and quote provider
Read Stockbit's public symbol page (__NEXT_DATA__) with a plain ureq request and an honest user agent: `stocks intraday` (today by the minute, any provider), `--provider stockbit` quotes with the best bid/offer only while the market is open, and MCP `get_intraday`. - UPSTREAMBLOCKED error (exit 4) for a CDN 403; never retried - requests paced 1 s apart across idx processes, 5 s snapshot cache - global --provider flag (yahoo, msn, stockbit) - roadmap: L1 ships from Stockbit; depth stays a login follow-on
This commit is contained in:
parent
018d9ba936
commit
74e5bb0536
65 changed files with 2320 additions and 381 deletions
|
|
@ -1,10 +1,10 @@
|
|||
//! Fixture-backed provider for tests, enabled by the `testing` feature and
|
||||
//! `IDX_USE_MOCK_PROVIDER`. Release builds do not contain it.
|
||||
|
||||
use super::types::{Bar, Fundamentals, Interval, Period, Quote};
|
||||
use super::types::{Bar, Fundamentals, Interval, Intraday, Period, Quote};
|
||||
use super::{
|
||||
BrokerSummaryProvider, FundamentalsProvider, HistoryProvider, QuoteProvider,
|
||||
TradingSummaryProvider, idx, ipot, msn, yahoo,
|
||||
BrokerSummaryProvider, FundamentalsProvider, HistoryProvider, IntradayProvider, QuoteProvider,
|
||||
TradingSummaryProvider, idx, ipot, msn, stockbit, yahoo,
|
||||
};
|
||||
use crate::broker::types::{BrokerBoard, BrokerInvestor, BrokerQuery};
|
||||
use crate::config::ProviderKind;
|
||||
|
|
@ -45,6 +45,10 @@ impl MockProvider {
|
|||
match provider {
|
||||
ProviderKind::Yahoo => Self::from_yahoo_fixtures(history_verbose),
|
||||
ProviderKind::Msn => Self::from_msn_fixtures(),
|
||||
// `MockStockbitProvider` serves Stockbit; it has no history.
|
||||
ProviderKind::Stockbit => Self::with_error(IdxError::Unsupported(
|
||||
"Stockbit serves no price history".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -131,6 +135,57 @@ impl HistoryProvider for MockProvider {
|
|||
}
|
||||
}
|
||||
|
||||
/// Stockbit symbol pages from `tests/fixtures/stockbit_symbol_<code>.html`;
|
||||
/// a code without a fixture is Stockbit's 404. `IDX_MOCK_STOCKBIT_STATUS`
|
||||
/// (e.g. `403`) answers that HTTP status instead, and `IDX_MOCK_ERROR` fails
|
||||
/// as for the other providers.
|
||||
pub struct MockStockbitProvider;
|
||||
|
||||
impl MockStockbitProvider {
|
||||
fn snapshot(&self, symbol: &str) -> Result<Intraday, IdxError> {
|
||||
if std::env::var("IDX_MOCK_ERROR").is_ok() {
|
||||
return Err(IdxError::ProviderUnavailable);
|
||||
}
|
||||
if let Ok(status) = std::env::var("IDX_MOCK_STOCKBIT_STATUS") {
|
||||
return Err(stockbit::status_error(
|
||||
status.parse().unwrap_or(500),
|
||||
symbol,
|
||||
));
|
||||
}
|
||||
let code = stockbit::stockbit_code(symbol)?;
|
||||
let path = format!(
|
||||
"tests/fixtures/stockbit_symbol_{}.html",
|
||||
code.to_ascii_lowercase()
|
||||
);
|
||||
match std::fs::read_to_string(path) {
|
||||
Ok(html) => stockbit::parse_symbol_page(symbol, &html),
|
||||
Err(_) => Err(stockbit::status_error(404, symbol)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl QuoteProvider for MockStockbitProvider {
|
||||
fn quote(&self, symbol: &str) -> Result<Quote, IdxError> {
|
||||
let mut quote = self.snapshot(symbol)?.quote;
|
||||
// Quotes are judged against the clock (behind the session, a week
|
||||
// old); a fixed trade time would make their output age.
|
||||
quote.as_of = None;
|
||||
Ok(quote)
|
||||
}
|
||||
}
|
||||
|
||||
impl FundamentalsProvider for MockStockbitProvider {
|
||||
fn fundamentals(&self, symbol: &str) -> Result<Fundamentals, IdxError> {
|
||||
Err(stockbit::fundamentals_unsupported(symbol))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntradayProvider for MockStockbitProvider {
|
||||
fn intraday(&self, symbol: &str) -> Result<Intraday, IdxError> {
|
||||
self.snapshot(symbol)
|
||||
}
|
||||
}
|
||||
|
||||
/// Broker summaries from `tests/fixtures/ipot_brokersummary_<code>_<from>
|
||||
/// [_<to>][_<investor>_<board>].html`; a query without a fixture reads as an
|
||||
/// unknown code.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue