mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-11-07 09:45:36 +00:00
fix(history): fall back to Yahoo when Stockbit refuses the session
auto history picks Stockbit when a session file exists, so an expired session failed history and technical with AUTHERROR. auto now retries on Yahoo with a warning; an explicit --history-provider stockbit still fails. Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
This commit is contained in:
parent
e7f1fa2c90
commit
63587e4dc2
4 changed files with 110 additions and 31 deletions
|
|
@ -128,22 +128,24 @@ impl Analysis<'_> {
|
|||
policy: CachePolicy,
|
||||
) -> Result<Fetched<TechnicalReport>, IdxError> {
|
||||
let idx = self.idx;
|
||||
let provider = history_provider(source.provider, idx.config.verbose);
|
||||
let bucket = format!("{}-technical", source.provider.as_str());
|
||||
let resolved = idx.resolve(symbol)?;
|
||||
cached_fetch(
|
||||
&idx.cache,
|
||||
CacheSpec {
|
||||
bucket: &bucket,
|
||||
key: &resolved,
|
||||
ttl_secs: idx.config.quote_ttl,
|
||||
subject: &resolved,
|
||||
source: source.provider.as_str(),
|
||||
},
|
||||
policy,
|
||||
|| provider.history(&resolved, &Period::OneYear, &Interval::Day),
|
||||
|history| build_technical_report(&resolved, &history),
|
||||
)
|
||||
idx.market().with_history_fallback(source, |source| {
|
||||
let provider = history_provider(source.provider, idx.config.verbose);
|
||||
let bucket = format!("{}-technical", source.provider.as_str());
|
||||
cached_fetch(
|
||||
&idx.cache,
|
||||
CacheSpec {
|
||||
bucket: &bucket,
|
||||
key: &resolved,
|
||||
ttl_secs: idx.config.quote_ttl,
|
||||
subject: &resolved,
|
||||
source: source.provider.as_str(),
|
||||
},
|
||||
policy,
|
||||
|| provider.history(&resolved, &Period::OneYear, &Interval::Day),
|
||||
|history| build_technical_report(&resolved, &history),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn growth(
|
||||
|
|
|
|||
|
|
@ -143,7 +143,8 @@ impl HistoryProvider for MockProvider {
|
|||
/// `stockbit_exodus_chart_candle_<code>_<window>.json`, with the market state
|
||||
/// from `stockbit_exodus_market_session.json`. 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.
|
||||
/// status instead (`401` on the session API: Stockbit refused the session),
|
||||
/// and `IDX_MOCK_ERROR` fails as for the other providers.
|
||||
pub struct MockStockbitProvider;
|
||||
|
||||
impl MockStockbitProvider {
|
||||
|
|
@ -178,6 +179,11 @@ impl MockStockbitProvider {
|
|||
if !stockbit::has_session() {
|
||||
return Err(IdxError::AuthError(stockbit::auth::NO_SESSION.to_string()));
|
||||
}
|
||||
if std::env::var("IDX_MOCK_STOCKBIT_STATUS").as_deref() == Ok("401") {
|
||||
return Err(IdxError::AuthError(
|
||||
"Stockbit rejected the session; run `idx stockbit login` again".to_string(),
|
||||
));
|
||||
}
|
||||
if let Some(err) = self.failure(symbol) {
|
||||
return Err(err);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,23 +127,49 @@ impl Market<'_> {
|
|||
policy: CachePolicy,
|
||||
) -> Result<Fetched<Vec<Ohlc>>, IdxError> {
|
||||
let idx = self.idx;
|
||||
let provider = history_provider(source.provider, idx.config.verbose);
|
||||
let bucket = format!("{}-history", source.provider.as_str());
|
||||
let resolved = idx.resolve(symbol)?;
|
||||
let key = format!("{resolved}-{}-{}", period.as_str(), interval.as_str());
|
||||
cached_fetch(
|
||||
&idx.cache,
|
||||
CacheSpec {
|
||||
bucket: &bucket,
|
||||
key: &key,
|
||||
ttl_secs: idx.config.quote_ttl,
|
||||
subject: &resolved,
|
||||
source: source.provider.as_str(),
|
||||
},
|
||||
policy,
|
||||
|| provider.history(&resolved, period, interval),
|
||||
Ok,
|
||||
)
|
||||
self.with_history_fallback(source, |source| {
|
||||
let provider = history_provider(source.provider, idx.config.verbose);
|
||||
let bucket = format!("{}-history", source.provider.as_str());
|
||||
cached_fetch(
|
||||
&idx.cache,
|
||||
CacheSpec {
|
||||
bucket: &bucket,
|
||||
key: &key,
|
||||
ttl_secs: idx.config.quote_ttl,
|
||||
subject: &resolved,
|
||||
source: source.provider.as_str(),
|
||||
},
|
||||
policy,
|
||||
|| provider.history(&resolved, period, interval),
|
||||
Ok,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Runs `fetch` on `source`; when `auto` picked Stockbit because a
|
||||
/// session file exists and Stockbit then refuses that session, runs it
|
||||
/// again on Yahoo instead of failing (`has_session` sees only the file).
|
||||
pub(crate) fn with_history_fallback<T>(
|
||||
&self,
|
||||
source: HistorySource,
|
||||
fetch: impl Fn(HistorySource) -> Result<Fetched<T>, IdxError>,
|
||||
) -> Result<Fetched<T>, IdxError> {
|
||||
match fetch(source) {
|
||||
Err(IdxError::AuthError(reason))
|
||||
if source.mode == HistoryProviderKind::Auto
|
||||
&& source.provider == ProviderKind::Stockbit =>
|
||||
{
|
||||
crate::runtime::warn(format!("{reason}; history falls back to yahoo meanwhile"));
|
||||
fetch(HistorySource {
|
||||
provider: ProviderKind::Yahoo,
|
||||
is_fallback: self.idx.provider.kind() != ProviderKind::Yahoo,
|
||||
..source
|
||||
})
|
||||
}
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
|
||||
/// The order book of one IDX stock from the user's Stockbit session,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue