mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-11-07 09:45:36 +00:00
feat(core): broker summaries from IPOT
Adds a BrokerSummaryProvider capability served by Indo Premier's public broker summary page: the top 10 buyers and sellers of a stock by lots over a date range, with totals. No login or key. - api/ipot: pure parser (decimal scaling of `1.6 M`-style figures), a client that retries the server's spurious 404s, and a fixture mock. - broker: Broker::summary/latest over the cache (7 days for closed ranges, 5 minutes for today), range checks, and IDX member names bundled from GetBrokerCodeList. - foreign_net_value is null for filtered summaries: the source does not apply the filters to it. The endpoint's behavior was first mapped by fzn0x/idx (MIT). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
This commit is contained in:
parent
ebdba13a41
commit
2c93c73b6a
16 changed files with 1767 additions and 1 deletions
62
crates/idx-core/src/api/ipot/client.rs
Normal file
62
crates/idx-core/src/api/ipot/client.rs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use crate::error::IdxError;
|
||||
|
||||
const USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36";
|
||||
/// Tries per request. In some stretches of seconds the server answers its
|
||||
/// own nginx 404 page to a third or more of requests and then recovers, so a
|
||||
/// 404 is retried (an unknown ticker is a 200, not a 404).
|
||||
const ATTEMPTS: u32 = 5;
|
||||
const FIRST_PAUSE: Duration = Duration::from_millis(250);
|
||||
|
||||
pub(super) struct IpotClient {
|
||||
agent: ureq::Agent,
|
||||
}
|
||||
|
||||
impl IpotClient {
|
||||
pub(super) fn new() -> Self {
|
||||
let agent: ureq::Agent = ureq::Agent::config_builder()
|
||||
.timeout_connect(Some(Duration::from_secs(5)))
|
||||
.timeout_recv_body(Some(Duration::from_secs(10)))
|
||||
// A server that stalls before sending headers must not hold a
|
||||
// call (and an MCP call slot) forever.
|
||||
.timeout_global(Some(Duration::from_secs(20)))
|
||||
.build()
|
||||
.into();
|
||||
Self { agent }
|
||||
}
|
||||
|
||||
pub(super) fn fetch_html(&self, url: &str) -> Result<String, IdxError> {
|
||||
let mut pause = FIRST_PAUSE;
|
||||
let mut last = None;
|
||||
for attempt in 1..=ATTEMPTS {
|
||||
if attempt > 1 {
|
||||
std::thread::sleep(pause);
|
||||
pause *= 2;
|
||||
}
|
||||
let response = self
|
||||
.agent
|
||||
.get(url)
|
||||
.header("User-Agent", USER_AGENT)
|
||||
.header("Accept", "text/html")
|
||||
.call();
|
||||
match response {
|
||||
Ok(ok) => {
|
||||
return ok
|
||||
.into_body()
|
||||
.read_to_string()
|
||||
.map_err(|e| IdxError::Http(format!("ipot broker summary: {e}")));
|
||||
}
|
||||
Err(ureq::Error::StatusCode(code)) if code == 404 || code >= 500 => {
|
||||
last = Some(code);
|
||||
}
|
||||
Err(ureq::Error::StatusCode(429)) => return Err(IdxError::RateLimited),
|
||||
Err(err) => return Err(IdxError::Http(format!("ipot broker summary: {err}"))),
|
||||
}
|
||||
}
|
||||
Err(IdxError::Http(format!(
|
||||
"ipot broker summary: status {} after {ATTEMPTS} attempts",
|
||||
last.unwrap_or_default()
|
||||
)))
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue