feat: corporate actions from the Stockbit session (stocks actions, stocks calendar)

`idx stocks actions SYM [--kind ...]`: one stock's cash and stock
dividends, bonus shares, rights, warrants, splits and reverse splits,
meetings, public exposes and tender offers (`--kind ipo` adds its IPO),
newest first. `idx stocks calendar [--from] [--to] [--kind ...]`: the
market's actions dated in the range (default today and 30 days ahead),
oldest first. MCP: get_corporate_actions, get_corporate_calendar.

Stockbit's `/corpaction/{type}` serves one kind per request; each type has
its own field prefix and list key, so entitlements are read by field
suffix. An action's `date` is the cum date for entitlements, else the
event's first day. Dividends keep their decimals. One request per kind,
cached 6 hours. AUTHERROR without a session.

Fixtures are the 2026-11-03 recordings (market-wide ones trimmed to
October-December). Goldens added; help-stocks and completions-bash gain
the two subcommands, MCP tools.json the two tools (regenerated with
UPDATE_GOLDEN=1). The MCP docs counted 17 tools without stockbit_login;
now 20.

Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
This commit is contained in:
Rasyidan Akbar F. 2026-11-03 14:35:20 +07:00
commit 43ff4aa433
55 changed files with 2026 additions and 34 deletions

View file

@ -1,14 +1,16 @@
//! Fixture-backed provider for tests, enabled by the `testing` feature and
//! `IDX_USE_MOCK_PROVIDER`. Release builds do not contain it.
use super::stockbit::exodus;
use super::stockbit::{corpaction, exodus};
use super::types::{Bar, Fundamentals, Interval, Intraday, OrderBook, Period, Quote};
use super::{
BrokerSummaryProvider, FundamentalsProvider, HistoryProvider, IntradayProvider,
OrderBookProvider, QuoteProvider, TradingSummaryProvider, idx, ipot, msn, stockbit, yahoo,
BrokerSummaryProvider, CorporateActionProvider, FundamentalsProvider, HistoryProvider,
IntradayProvider, OrderBookProvider, QuoteProvider, TradingSummaryProvider, idx, ipot, msn,
stockbit, yahoo,
};
use crate::broker::types::{BrokerBoard, BrokerInvestor, BrokerQuery};
use crate::config::ProviderKind;
use crate::corporate::types::{ActionKind, CorporateAction};
use crate::error::IdxError;
pub(crate) fn enabled() -> bool {
@ -224,6 +226,34 @@ impl OrderBookProvider for MockStockbitProvider {
}
}
/// `stockbit_exodus_corpaction_<type>.json` for the market,
/// `..._<type>_<code>.json` for one stock; a stock without a fixture has none
/// of that kind, as Stockbit answers.
impl CorporateActionProvider for MockStockbitProvider {
fn corporate_actions(
&self,
kind: ActionKind,
symbol: Option<&str>,
) -> Result<Vec<CorporateAction>, IdxError> {
let subject = symbol.unwrap_or("corporate actions");
let name = match symbol {
Some(symbol) => format!(
"corpaction_{}_{}.json",
corpaction::path_type(kind),
stockbit::stockbit_code(symbol)?.to_ascii_lowercase()
),
None => format!("corpaction_{}.json", corpaction::path_type(kind)),
};
let body = match self.exodus(subject, &name) {
Err(IdxError::SymbolNotFound(_)) if symbol.is_some() => {
r#"{"data":{"none":[]}}"#.to_string()
}
other => other?,
};
corpaction::parse_corporate_actions(kind, &body)
}
}
impl HistoryProvider for MockStockbitProvider {
fn history(
&self,