fix(actions): keep small foreign dividends, allow only read paths, show open offers

From Pullfrog's review of #78:

- A dividend in another currency keeps 6 decimals (USD 0.0012 was rounded
  to 0.0 in JSON and "USD 0" in the table); rupiah stays to the sen.
- `--kind stock_dividend` and `--kind reverse_split` work on the CLI too,
  as they already did over MCP.
- The session client GETs only the prefixes in READ_PATHS, as
  docs/STOCKBIT.md's rule 1 already said: any other path (orders, PIN,
  withdrawals) fails before a request or token refresh leaves the process.
- `stocks calendar` and get_corporate_calendar also list rights trading,
  tender offers and IPO offers still open in the range, so an offer that
  began earlier shows while it can be acted on. A warrant's years-long
  windows do not count. The 2026-11-03..12 golden gains DOOH, NAYZ and AUTO.
- `stocks calendar --help` names the ex-date fallback, and docs/STOCKBIT.md
  shows the record and surface as shipped.

Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
This commit is contained in:
Rasyidan Akbar F. 2026-11-04 20:44:07 +07:00
commit 884c6459e4
16 changed files with 247 additions and 67 deletions

View file

@ -296,8 +296,10 @@ WIB as the fallback.
request per kind, cached 6 hours under `stockbit-corpaction`
(`<kind>:<SYMBOL.JK>` or `<kind>:market`); `stocks actions` /
`get_corporate_actions` sort newest first, `stocks calendar` /
`get_corporate_calendar` filter by `CorporateAction::date` (the cum date
for entitlements) and sort oldest first. Endpoint notes and the wider
`get_corporate_calendar` keep what `CalendarRange::includes`: a
`CorporateAction::date` (the cum date for entitlements) in the range, or a
rights, tender or IPO `period` that overlaps it (not a warrant's, which
runs for years), sorted oldest first. Endpoint notes and the wider
design: `docs/STOCKBIT.md`.
### Capability Gating

View file

@ -10,7 +10,8 @@ one GET at a time at least 1 s apart. Nothing here ships yet unless marked.
## Rules for every session feature
1. **Read-only by construction.** The session client (`api/stockbit/exodus.rs`)
issues GET on an allowlist of path prefixes, plus `POST /login/refresh`.
issues GET only on an allowlist of path prefixes (`READ_PATHS`), plus
`POST /login/refresh`.
The bundle also holds trading (orders, day trade, PIN, cash withdrawal,
portfolio), chat, posting and account endpoints; idx never calls them, and
the allowlist makes a stray call fail before it leaves the process.
@ -134,18 +135,32 @@ Examples from 2026-11-03: PPGL bonus 6:4 ex 2026-11-03; PRDA split 1:5 cum
### Types
As shipped (`corporate/types.rs`), one flat record with a calendar `date` and
optional per-kind fields; JSON leaves absent ones out:
```rust
pub struct CorporateAction {
pub symbol: String, // BBCA.JK
pub kind: ActionKind, // Dividend, StockDividend, Bonus, Rights, Warrant,
// Split, ReverseSplit, Meeting, PublicExpose, Ipo, TenderOffer
pub symbol: String, // BBCA.JK
pub kind: ActionKind, // JSON: dividend, stock-dividend, bonus, rights, warrant,
// split, reverse-split, meeting, expose, ipo, tender
pub date: NaiveDate, // cum date for entitlements (ex date without one),
// else the event's first day
pub cum_date: Option<NaiveDate>,
pub ex_date: Option<NaiveDate>,
pub record_date: Option<NaiveDate>,
pub pay_date: Option<NaiveDate>,
pub event_date: Option<NaiveDate>, // meeting, expose, listing, tender end
pub detail: ActionDetail, // kind-specific: amount + currency; old:new ratio
// and factor; price; trading/exercise windows; venue
pub cash_per_share: Option<f64>, // dividends; IDR to the sen, else up to 6 decimals
pub currency: Option<String>, // dividends not paid in IDR
pub ratio: Option<Ratio>, // old : new
pub factor: Option<f64>, // Stockbit's price adjustment
pub price: Option<i64>, // rights, warrant, tender or IPO price (IDR)
pub period: Option<DateRange>, // rights or warrant trading, tender, IPO offer
pub exercise: Option<DateRange>, // warrant exercise
pub shares: Option<u64>, // offered (IPO) or sought (tender)
pub percentage_bps: Option<i64>, // tender
pub time: Option<String>, // meeting or expose
pub venue: Option<String>,
pub name: Option<String>, // IPO or tender issuer
}
```
@ -157,8 +172,9 @@ lists are not errors. A row whose dates are all placeholders is dropped.
- `idx stocks actions SYM [--kind dividend,rights,...]`: one stock's
history, newest first; every kind but `ipo` by default.
- `idx stocks calendar [--kind ...] [--from DATE] [--to DATE]`: market-wide,
default today to +30 days, oldest first by `date`.
- MCP: `get_corporate_actions {symbol, kinds?, since?}`,
default today to +30 days, oldest first by `date`; rights trading, tender
offers and IPO offers still open in the range are included too.
- MCP: `get_corporate_actions {symbol, kinds?}`,
`get_corporate_calendar {kinds?, from?, to?}`.
- Later: `stocks history` notes ex-dates of splits, bonuses and rights in the
window, so a gap is not read as a crash.