feat: bandar and broker totals for one investor side

IPOT's investor filter splits each broker row by client domicile, but its
footer then gives only that side's buying (BBCA 2026-10-16: foreign 1.7 T,
domestic 4.9 T of 6.6 T). The foreign net gives the selling on all boards:
foreign 1.7 + 2.3 = 4.0 T (IDX: about 3.95 T regular), domestic
4.9 - 2.3 = 2.6 T.

- BrokerSummary gains buy_value and sell_value: the total for all
  investors, the footer and the derived selling for one side (selling is
  null with a board filter, where the net covers other boards).
- Bandar weighs the top N buyers' share of the value bought against the
  top N sellers' share of the value sold, so `--investor foreign|domestic`
  reads that side alone; for all investors the readings are unchanged.
  One side on one board is refused, as there is no selling to weigh.
- Tables print what the side bought and sold; fixtures for foreign and
  domestic on all boards were recorded from IPOT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
This commit is contained in:
hermes 2026-10-18 06:08:02 +00:00
commit 5f64319157
23 changed files with 717 additions and 86 deletions

View file

@ -175,8 +175,11 @@ for one or more IDX tickers."
by lots over a session or date range, each with lots (1 lot = 100 shares), value and average \
price in IDR, plus totals. Buyers and sellers are ranked separately and gross, so a broker can \
appear on both sides. End of day only (IDX hides broker codes during trading); `kind` is the \
broker's own status (local, foreign, state_owned), not its clients'. `rounded` = true means \
figures of a million and more carry one decimal of precision."
broker's own status (local, foreign, state_owned), not its clients'. With `investor` foreign \
or domestic, each row is that broker's foreign or domestic clients' trades, `total_value` is \
null, and `buy_value`/`sell_value` give what that side bought and sold (`sell_value` needs \
`board` all). `rounded` = true means figures of a million and more carry one decimal of \
precision."
)]
async fn get_broker_summary(&self, params: Parameters<BrokerParams>) -> ToolResult {
let BrokerParams { symbol, scope } = params.0;
@ -206,8 +209,9 @@ traded (`imbalance_bps`), with a `signal` from big_accumulation (>= +1500) throu
(within +-500) to big_distribution (<= -1500), headlined by `top` (default 5). Reports come \
strongest accumulation first; tickers that fail are listed under `failures`. A heuristic over \
end-of-day broker codes: brokers trade for many clients, and `both_sides` lists brokers whose \
gross figures overstate their direction. `method` names the rules applied. `investor` must be \
`all`: the summary has no total for one investor side."
gross figures overstate their direction. `method` names the rules applied. With `investor` \
foreign or domestic, shares are of that side's own buying (`buy_value`) and selling \
(`sell_value`); that needs `board` all."
)]
async fn analyze_bandar(&self, params: Parameters<BandarParams>) -> ToolResult {
let BandarParams {