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

@ -78,6 +78,17 @@ pub trait OrderBookProvider {
fn orderbook(&self, symbol: &str) -> Result<OrderBook, IdxError>;
}
/// Corporate actions of one kind: needs the user's Stockbit session.
pub trait CorporateActionProvider {
/// One stock's history (`symbol` resolved, e.g. `BBCA.JK`), or the
/// market's announced and recent actions with `None`.
fn corporate_actions(
&self,
kind: crate::corporate::types::ActionKind,
symbol: Option<&str>,
) -> Result<Vec<crate::corporate::types::CorporateAction>, IdxError>;
}
pub trait FundamentalsProvider {
fn fundamentals(&self, symbol: &str) -> Result<Fundamentals, IdxError>;
}
@ -345,6 +356,14 @@ pub fn orderbook_provider() -> Box<dyn OrderBookProvider> {
Box::new(stockbit::StockbitProvider::new())
}
pub fn corporate_action_provider() -> Box<dyn CorporateActionProvider> {
#[cfg(feature = "testing")]
if mock::enabled() {
return Box::new(mock::MockStockbitProvider);
}
Box::new(stockbit::StockbitProvider::new())
}
/// Today in WIB, the date IDX trades on.
pub(crate) fn today_wib() -> chrono::NaiveDate {
#[cfg(feature = "testing")]