feat: foreign flow from IPOT by default (--source ipot|idx)

`idx stocks flow` now reads IPOT by default, which needs nothing installed:
per session, the all-investor broker summary gives the value traded and
the foreign one gives foreign buying (its footer), the foreign net and
selling (buying less the net), across all boards, through the broker
summary cache. IPOT's foreign net matched IDX's estimate within 0.3 B on
BBCA 2026-10-12 to 16.

`--source idx` (and `source` on get_foreign_flow) keeps IDX's trading
summary: exact share counts, close and the non-regular share, behind
curl-impersonate and Cloudflare.

- ForeignFlowDay gains foreign_buy_value, foreign_sell_value and
  foreign_share_bps for both sources (estimated at the session average
  for IDX); ForeignFlow gains source, the value totals and rounded.
- The table lays out each source's figures; the live smoke runs both.

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:19:02 +00:00
commit 8b507d78e2
37 changed files with 2080 additions and 143 deletions

View file

@ -36,8 +36,9 @@ history lives in git.
- `stocks broker` and `stocks bandar` show only IPOT's top 10 buyers and
sellers, ranked by gross lots, with figures of a million and more rounded
to one decimal.
- `stocks flow` depends on IDX's Cloudflare passing a curl-impersonate
profile (only `curl_safari184` in October 2026).
- `stocks flow --source idx` depends on IDX's Cloudflare passing a
curl-impersonate profile (only `curl_safari184` in October 2026) and on the
network; the default IPOT source rounds to one decimal.
- Yahoo can return `429` from datacenter IPs; requests retry with backoff but
can still fail.
- `stocks technical` reports the SMA200 trend as unavailable when fewer than

View file

@ -15,7 +15,7 @@ use idx_core::api::types::{Interval, Period};
use idx_core::broker::types::{BrokerBoard, BrokerInvestor, BrokerRange};
use idx_core::error::IdxError;
use idx_core::financial::{EarningsFilter, StatementFilter, StatementKind};
use idx_core::flow::types::FlowRange;
use idx_core::flow::types::{FlowRange, FlowSource};
use idx_core::market::ScreenQuery;
use idx_core::{CachePolicy, Idx, runtime};
@ -224,11 +224,13 @@ heuristic over end-of-day broker codes, not advice.",
},
#[command(
about = "Foreign buying and selling of a stock, session by session",
long_about = "Foreign buying and selling of a stock, session by session, from IDX's daily \
trading summary: foreign buy, sell and net volume, the net value at the session's average \
price, and the share of value traded outside the regular board. End of day. Each session is \
one request to idx.co.id, which needs curl-impersonate.",
after_help = "Examples:\n idx stocks flow BBCA\n idx stocks flow BBCA --days 20\n idx stocks flow BBCA --from 2026-10-01 --to 2026-10-16\n idx -o json stocks flow BBCA | jq '.foreign_net'"
long_about = "Foreign buying and selling of a stock, session by session, end of day. By \
default from Indo Premier (IPOT): foreign buy, sell and net value across all boards and \
foreign investors' share of trading, two plain requests per session. `--source idx` reads \
IDX's own trading summary instead: exact foreign share counts, the close and the share \
traded outside the regular board, one request per session to idx.co.id, which needs \
curl-impersonate and a network IDX's Cloudflare lets through.",
after_help = "Examples:\n idx stocks flow BBCA\n idx stocks flow BBCA --days 20\n idx stocks flow BBCA --from 2026-10-01 --to 2026-10-16\n idx stocks flow BBCA --source idx\n idx -o json stocks flow BBCA | jq '.foreign_net_value'"
)]
Flow {
/// Single ticker symbol (e.g. BBCA).
@ -242,6 +244,9 @@ one request to idx.co.id, which needs curl-impersonate.",
/// Last session of the range, YYYY-MM-DD. Default: today.
#[arg(long, requires = "from")]
to: Option<String>,
/// Where the figures come from.
#[arg(long, value_enum, default_value_t = FlowSource::Ipot)]
source: FlowSource,
},
#[command(about = "Get company profile")]
Profile { symbol: String },
@ -389,10 +394,11 @@ pub fn handle(
days,
from,
to,
source,
} => {
let range =
FlowRange::from_args(*days, from.as_deref(), to.as_deref(), idx.flow().today())?;
let flow = idx.flow().foreign(symbol, range, policy)?;
let flow = idx.flow().foreign(symbol, range, *source, policy)?;
render_foreign_flow(&flow.data, format)
}
StocksSubcommand::Profile { symbol } => {

View file

@ -16,7 +16,7 @@ use idx_core::api::types::{
};
use idx_core::broker::types::{BrokerBoard, BrokerFlow, BrokerInvestor, BrokerKind, BrokerSummary};
use idx_core::error::IdxError;
use idx_core::flow::types::ForeignFlow;
use idx_core::flow::types::{FlowSource, ForeignFlow};
pub fn format_idr(value: i64) -> String {
let sign = if value.is_negative() { "-" } else { "" };
@ -1262,12 +1262,70 @@ pub fn print_foreign_flow(flow: &ForeignFlow) -> Result<(), IdxError> {
println!(
"{}",
format!(
"Foreign flow for {}, {span} ({sessions} session{})",
"Foreign flow for {}, {span} ({sessions} session{}, {})",
terminal_safe(&flow.symbol),
if sessions == 1 { "" } else { "s" }
if sessions == 1 { "" } else { "s" },
match flow.source {
FlowSource::Ipot => "IPOT",
FlowSource::Idx => "IDX",
}
)
.bold()
);
match flow.source {
FlowSource::Ipot => print_ipot_flow(flow),
FlowSource::Idx => print_idx_flow(flow),
}
if flow.rounded {
println!("Note: the source rounds figures of a million and more to one decimal.");
}
Ok(())
}
fn print_ipot_flow(flow: &ForeignFlow) {
let mut table = Table::new();
table
.load_preset(UTF8_FULL)
.set_content_arrangement(ContentArrangement::Dynamic)
.set_header(vec![
"DATE",
"VALUE",
"FOREIGN BUY",
"FOREIGN SELL",
"FOREIGN NET",
"FOREIGN %",
]);
for day in &flow.days {
table.add_row(vec![
cell(day.date),
cell(format_compact_option(day.value)),
cell(format_compact_option(day.foreign_buy_value)),
cell(format_compact_option(day.foreign_sell_value)),
cell(format_signed_compact(day.foreign_net_value)),
cell(format_bps_share(day.foreign_share_bps)),
]);
}
println!("{table}");
let mut summary = format!(
"Foreign investors bought {} IDR and sold {} IDR: net {}",
format_compact_option(flow.foreign_buy_value),
format_compact_option(flow.foreign_sell_value),
format_signed_compact(flow.foreign_net_value)
);
if let Some(share) = flow.foreign_share_bps {
summary.push_str(&format!(
"; they made up {} of trading",
format_bps_share(Some(share))
));
}
println!("{summary}.");
println!(
"Values in IDR across all boards. For exact share counts, the close and the non-regular \
share, use --source idx (needs curl-impersonate)."
);
}
fn print_idx_flow(flow: &ForeignFlow) {
let mut table = Table::new();
table
.load_preset(UTF8_FULL)
@ -1314,7 +1372,6 @@ pub fn print_foreign_flow(flow: &ForeignFlow) -> Result<(), IdxError> {
is estimated at each session's average price; NON-REG is the share of all value traded \
outside the regular board (negotiated and cash)."
);
Ok(())
}
#[cfg(test)]

View file

@ -2758,11 +2758,13 @@ fn bandar_errors_follow_the_output_contract() {
}
#[test]
fn foreign_flow_reads_sessions_oldest_first() {
fn foreign_flow_from_idx_reads_sessions_oldest_first() {
let flow = run_success_json(
mock_bin("flow-json")
.env("IDX_MOCK_TODAY", "2026-10-18")
.args(["-o", "json", "stocks", "flow", "BBCA", "--days", "3"]),
.args([
"-o", "json", "stocks", "flow", "BBCA", "--days", "3", "--source", "idx",
]),
);
assert_eq!(flow["symbol"], "BBCA.JK");
let dates: Vec<&str> = flow["days"]
@ -2787,6 +2789,8 @@ fn foreign_flow_reads_sessions_oldest_first() {
"json",
"stocks",
"flow",
"--source",
"idx",
"BBCA",
"--from",
"2026-10-12",
@ -2805,7 +2809,7 @@ fn foreign_flow_reads_sessions_oldest_first() {
}
#[test]
fn foreign_flow_offline_uses_cached_sessions_only() {
fn foreign_flow_from_idx_offline_uses_cached_sessions_only() {
let root = test_env_dir("flow-offline");
run_success_json(
mock_bin_with_root(&root)
@ -2815,6 +2819,8 @@ fn foreign_flow_offline_uses_cached_sessions_only() {
"json",
"stocks",
"flow",
"--source",
"idx",
"BBCA",
"--from",
"2026-10-15",
@ -2832,6 +2838,8 @@ fn foreign_flow_offline_uses_cached_sessions_only() {
"--offline",
"stocks",
"flow",
"--source",
"idx",
"TLKM",
"--days",
"5",
@ -2848,6 +2856,8 @@ fn foreign_flow_offline_uses_cached_sessions_only() {
"--offline",
"stocks",
"flow",
"--source",
"idx",
"TLKM",
"--from",
"2026-10-12",
@ -2858,6 +2868,52 @@ fn foreign_flow_offline_uses_cached_sessions_only() {
);
}
#[test]
fn foreign_flow_from_ipot_by_default_gives_values_across_all_boards() {
let flow = run_success_json(
mock_bin("flow-ipot")
.env("IDX_MOCK_TODAY", "2026-10-18")
.args([
"-o",
"json",
"stocks",
"flow",
"BBCA",
"--from",
"2026-10-12",
"--to",
"2026-10-17",
]),
);
assert_eq!(flow["source"], "ipot");
assert_eq!(flow["rounded"], true);
let days = flow["days"].as_array().unwrap();
assert_eq!(days.len(), 5, "the Saturday had no trading");
let last = &days[4];
assert_eq!(last["date"], "2026-10-16");
assert_eq!(last["value"], 6_600_000_000_000_i64);
assert_eq!(last["foreign_buy_value"], 1_700_000_000_000_i64);
assert_eq!(last["foreign_sell_value"], 4_000_000_000_000_i64);
assert_eq!(last["foreign_net_value"], -2_300_000_000_000_i64);
for field in ["close", "foreign_buy", "foreign_net", "non_regular_bps"] {
assert!(last[field].is_null(), "{field}");
}
let net: i64 = days
.iter()
.map(|day| day["foreign_net_value"].as_i64().unwrap())
.sum();
assert_eq!(flow["foreign_net_value"], net);
assert!(flow["foreign_net"].is_null());
let table = run_success_stdout(
mock_bin("flow-ipot-table")
.env("IDX_MOCK_TODAY", "2026-10-18")
.args(["stocks", "flow", "BBCA", "--days", "1"]),
);
assert!(table.contains("(1 session, IPOT)"), "{table}");
assert!(table.contains("FOREIGN %"), "{table}");
}
#[test]
fn foreign_flow_errors_follow_the_output_contract() {
for (args, code) in [
@ -2873,6 +2929,11 @@ fn foreign_flow_errors_follow_the_output_contract() {
vec!["BBCA", "--from", "2026-06-01", "--to", "2026-10-16"],
"INVALIDINPUT",
),
(vec!["BBCA", "--source", "stockbit"], "INVALIDINPUT"),
(
vec!["ZZZZ", "--days", "2", "--source", "idx"],
"SYMBOLNOTFOUND",
),
] {
let mut cmd = mock_bin("flow-errors");
cmd.env("IDX_MOCK_TODAY", "2026-10-18")

View file

@ -273,8 +273,13 @@ formats = ["table", "json", "jsonl"]
args = ["stocks", "flow", "BBCA", "--from", "2026-10-12", "--to", "2026-10-16"]
[[case]]
name = "stocks-flow-latest"
args = ["stocks", "flow", "TLKM", "--days", "3"]
name = "stocks-flow-idx"
formats = ["table", "json", "jsonl"]
args = ["stocks", "flow", "BBCA", "--from", "2026-10-12", "--to", "2026-10-16", "--source", "idx"]
[[case]]
name = "stocks-flow-idx-latest"
args = ["stocks", "flow", "TLKM", "--days", "3", "--source", "idx"]
env = { IDX_MOCK_TODAY = "2026-10-18" }
[[case]]

View file

@ -2442,7 +2442,7 @@ _idx() {
return 0
;;
idx__stocks__flow)
opts="-o -q -v -h --days --from --to --output --no-color --quiet --verbose --offline --no-cache --help <SYMBOL>"
opts="-o -q -v -h --days --from --to --source --output --no-color --quiet --verbose --offline --no-cache --help <SYMBOL>"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
@ -2460,6 +2460,10 @@ _idx() {
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--source)
COMPREPLY=($(compgen -W "ipot idx" -- "${cur}"))
return 0
;;
--output)
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0

View file

@ -0,0 +1,4 @@
exit: 0
--- stdout
{"symbol":"TLKM.JK","source":"idx","from":"2026-10-14","to":"2026-10-16","days":[{"date":"2026-10-14","close":2250,"change_bps":45,"volume":87159900,"value":197725738000,"foreign_buy":63030200,"foreign_sell":60811200,"foreign_net":2219000,"foreign_buy_value":142986543253,"foreign_sell_value":137952652523,"foreign_net_value":5033890729,"foreign_share_bps":7104,"non_regular_bps":979},{"date":"2026-10-15","close":2230,"change_bps":-89,"volume":150485300,"value":333287991000,"foreign_buy":97385100,"foreign_sell":105521800,"foreign_net":-8136700,"foreign_buy_value":215684085637,"foreign_sell_value":233704878341,"foreign_net_value":-18020792704,"foreign_share_bps":6742,"non_regular_bps":78},{"date":"2026-10-16","close":1900,"change_bps":-1480,"volume":963614200,"value":1855341292500,"foreign_buy":679004400,"foreign_sell":727289800,"foreign_net":-48285400,"foreign_buy_value":1307354023123,"foreign_sell_value":1400322657713,"foreign_net_value":-92968634589,"foreign_share_bps":7297,"non_regular_bps":1485}],"foreign_net":-54203100,"foreign_buy_value":1666024652013,"foreign_sell_value":1771980188577,"foreign_net_value":-105955536564,"foreign_share_bps":7213,"rounded":false}
--- stderr

View file

@ -1,6 +1,6 @@
exit: 0
--- stdout
Foreign flow for TLKM.JK, 2026-10-14 to 2026-10-16 (3 sessions)
Foreign flow for TLKM.JK, 2026-10-14 to 2026-10-16 (3 sessions, IDX)
┌────────────┬───────┬────────┬────────┬─────────────┬──────────────┬─────────────┬───────────┬─────────┐
│ DATE ┆ CLOSE ┆ CHG% ┆ VALUE ┆ FOREIGN BUY ┆ FOREIGN SELL ┆ FOREIGN NET ┆ NET VALUE ┆ NON-REG │
╞════════════╪═══════╪════════╪════════╪═════════════╪══════════════╪═════════════╪═══════════╪═════════╡

View file

@ -0,0 +1,4 @@
exit: 0
--- stdout
{"symbol":"BBCA.JK","source":"idx","from":"2026-10-12","to":"2026-10-16","days":[{"date":"2026-10-12","close":5950,"change_bps":-165,"volume":97965100,"value":586491892500,"foreign_buy":51499200,"foreign_sell":60883100,"foreign_net":-9383900,"foreign_buy_value":308312483427,"foreign_sell_value":364491482581,"foreign_net_value":-56178999154,"foreign_share_bps":5736,"non_regular_bps":14},{"date":"2026-10-13","close":5950,"change_bps":0,"volume":122716900,"value":726575115000,"foreign_buy":88772100,"foreign_sell":83738900,"foreign_net":5033200,"foreign_buy_value":525596708899,"foreign_sell_value":495796429810,"foreign_net_value":29800279088,"foreign_share_bps":7029,"non_regular_bps":159},{"date":"2026-10-14","close":6125,"change_bps":294,"volume":136303100,"value":828862772500,"foreign_buy":119876900,"foreign_sell":84298000,"foreign_net":35578900,"foreign_buy_value":728974613876,"foreign_sell_value":512618377690,"foreign_net_value":216356236186,"foreign_share_bps":7490,"non_regular_bps":2412},{"date":"2026-10-15","close":6075,"change_bps":-82,"volume":134763200,"value":816554920000,"foreign_buy":120850000,"foreign_sell":73638900,"foreign_net":47211100,"foreign_buy_value":732252292035,"foreign_sell_value":446191587157,"foreign_net_value":286060704877,"foreign_share_bps":7216,"non_regular_bps":1046},{"date":"2026-10-16","close":5175,"change_bps":-1481,"volume":1241098200,"value":6474934315000,"foreign_buy":309392800,"foreign_sell":757227900,"foreign_net":-447835100,"foreign_buy_value":1614133400188,"foreign_sell_value":3950534223629,"foreign_net_value":-2336400823441,"foreign_share_bps":4297,"non_regular_bps":152}],"foreign_net":-369395800,"foreign_buy_value":3909269498425,"foreign_sell_value":5769632100867,"foreign_net_value":-1860362602444,"foreign_share_bps":5050,"rounded":false}
--- stderr

View file

@ -0,0 +1,4 @@
exit: 0
--- stdout
{"symbol":"BBCA.JK","source":"idx","from":"2026-10-12","to":"2026-10-16","days":[{"date":"2026-10-12","close":5950,"change_bps":-165,"volume":97965100,"value":586491892500,"foreign_buy":51499200,"foreign_sell":60883100,"foreign_net":-9383900,"foreign_buy_value":308312483427,"foreign_sell_value":364491482581,"foreign_net_value":-56178999154,"foreign_share_bps":5736,"non_regular_bps":14},{"date":"2026-10-13","close":5950,"change_bps":0,"volume":122716900,"value":726575115000,"foreign_buy":88772100,"foreign_sell":83738900,"foreign_net":5033200,"foreign_buy_value":525596708899,"foreign_sell_value":495796429810,"foreign_net_value":29800279088,"foreign_share_bps":7029,"non_regular_bps":159},{"date":"2026-10-14","close":6125,"change_bps":294,"volume":136303100,"value":828862772500,"foreign_buy":119876900,"foreign_sell":84298000,"foreign_net":35578900,"foreign_buy_value":728974613876,"foreign_sell_value":512618377690,"foreign_net_value":216356236186,"foreign_share_bps":7490,"non_regular_bps":2412},{"date":"2026-10-15","close":6075,"change_bps":-82,"volume":134763200,"value":816554920000,"foreign_buy":120850000,"foreign_sell":73638900,"foreign_net":47211100,"foreign_buy_value":732252292035,"foreign_sell_value":446191587157,"foreign_net_value":286060704877,"foreign_share_bps":7216,"non_regular_bps":1046},{"date":"2026-10-16","close":5175,"change_bps":-1481,"volume":1241098200,"value":6474934315000,"foreign_buy":309392800,"foreign_sell":757227900,"foreign_net":-447835100,"foreign_buy_value":1614133400188,"foreign_sell_value":3950534223629,"foreign_net_value":-2336400823441,"foreign_share_bps":4297,"non_regular_bps":152}],"foreign_net":-369395800,"foreign_buy_value":3909269498425,"foreign_sell_value":5769632100867,"foreign_net_value":-1860362602444,"foreign_share_bps":5050,"rounded":false}
--- stderr

View file

@ -0,0 +1,19 @@
exit: 0
--- stdout
Foreign flow for BBCA.JK, 2026-10-12 to 2026-10-16 (5 sessions, IDX)
┌────────────┬───────┬────────┬────────┬─────────────┬──────────────┬─────────────┬───────────┬─────────┐
│ DATE ┆ CLOSE ┆ CHG% ┆ VALUE ┆ FOREIGN BUY ┆ FOREIGN SELL ┆ FOREIGN NET ┆ NET VALUE ┆ NON-REG │
╞════════════╪═══════╪════════╪════════╪═════════════╪══════════════╪═════════════╪═══════════╪═════════╡
│ 2026-10-12 ┆ 5,950 ┆ -1.6% ┆ 586.5B ┆ 51.5M ┆ 60.9M ┆ -9.4M ┆ -56.2B ┆ 0.1% │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
│ 2026-10-13 ┆ 5,950 ┆ +0.0% ┆ 726.6B ┆ 88.8M ┆ 83.7M ┆ +5.0M ┆ +29.8B ┆ 1.6% │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
│ 2026-10-14 ┆ 6,125 ┆ +2.9% ┆ 828.9B ┆ 119.9M ┆ 84.3M ┆ +35.6M ┆ +216.4B ┆ 24.1% │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
│ 2026-10-15 ┆ 6,075 ┆ -0.8% ┆ 816.6B ┆ 120.8M ┆ 73.6M ┆ +47.2M ┆ +286.1B ┆ 10.5% │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
│ 2026-10-16 ┆ 5,175 ┆ -14.8% ┆ 6.5T ┆ 309.4M ┆ 757.2M ┆ -447.8M ┆ -2.3T ┆ 1.5% │
└────────────┴───────┴────────┴────────┴─────────────┴──────────────┴─────────────┴───────────┴─────────┘
Net foreign: -369.4M shares, about -1.9T IDR; foreign investors made up 50.5% of trading.
Volume, value and foreign figures are the regular board, volumes in shares. Net value is estimated at each session's average price; NON-REG is the share of all value traded outside the regular board (negotiated and cash).
--- stderr

View file

@ -1,4 +0,0 @@
exit: 0
--- stdout
{"symbol":"TLKM.JK","from":"2026-10-14","to":"2026-10-16","days":[{"date":"2026-10-14","close":2250,"change_bps":45,"volume":87159900,"value":197725738000,"foreign_buy":63030200,"foreign_sell":60811200,"foreign_net":2219000,"foreign_net_value":5033890729,"non_regular_bps":979},{"date":"2026-10-15","close":2230,"change_bps":-89,"volume":150485300,"value":333287991000,"foreign_buy":97385100,"foreign_sell":105521800,"foreign_net":-8136700,"foreign_net_value":-18020792704,"non_regular_bps":78},{"date":"2026-10-16","close":1900,"change_bps":-1480,"volume":963614200,"value":1855341292500,"foreign_buy":679004400,"foreign_sell":727289800,"foreign_net":-48285400,"foreign_net_value":-92968634589,"non_regular_bps":1485}],"foreign_net":-54203100,"foreign_net_value":-105955536564,"foreign_share_bps":7213}
--- stderr

View file

@ -1,4 +1,4 @@
exit: 0
--- stdout
{"symbol":"BBCA.JK","from":"2026-10-12","to":"2026-10-16","days":[{"date":"2026-10-12","close":5950,"change_bps":-165,"volume":97965100,"value":586491892500,"foreign_buy":51499200,"foreign_sell":60883100,"foreign_net":-9383900,"foreign_net_value":-56178999154,"non_regular_bps":14},{"date":"2026-10-13","close":5950,"change_bps":0,"volume":122716900,"value":726575115000,"foreign_buy":88772100,"foreign_sell":83738900,"foreign_net":5033200,"foreign_net_value":29800279088,"non_regular_bps":159},{"date":"2026-10-14","close":6125,"change_bps":294,"volume":136303100,"value":828862772500,"foreign_buy":119876900,"foreign_sell":84298000,"foreign_net":35578900,"foreign_net_value":216356236186,"non_regular_bps":2412},{"date":"2026-10-15","close":6075,"change_bps":-82,"volume":134763200,"value":816554920000,"foreign_buy":120850000,"foreign_sell":73638900,"foreign_net":47211100,"foreign_net_value":286060704877,"non_regular_bps":1046},{"date":"2026-10-16","close":5175,"change_bps":-1481,"volume":1241098200,"value":6474934315000,"foreign_buy":309392800,"foreign_sell":757227900,"foreign_net":-447835100,"foreign_net_value":-2336400823441,"non_regular_bps":152}],"foreign_net":-369395800,"foreign_net_value":-1860362602444,"foreign_share_bps":5050}
{"symbol":"BBCA.JK","source":"ipot","from":"2026-10-12","to":"2026-10-16","days":[{"date":"2026-10-12","close":null,"change_bps":null,"volume":98041500,"value":587100000000,"foreign_buy":null,"foreign_sell":null,"foreign_net":null,"foreign_buy_value":308700000000,"foreign_sell_value":364600000000,"foreign_net_value":-55900000000,"foreign_share_bps":5734,"non_regular_bps":null},{"date":"2026-10-13","close":null,"change_bps":null,"volume":120000000,"value":738200000000,"foreign_buy":null,"foreign_sell":null,"foreign_net":null,"foreign_buy_value":537300000000,"foreign_sell_value":507200000000,"foreign_net_value":30100000000,"foreign_share_bps":7075,"non_regular_bps":null},{"date":"2026-10-14","close":null,"change_bps":null,"volume":180000000,"value":1100000000000,"foreign_buy":null,"foreign_sell":null,"foreign_net":null,"foreign_buy_value":991800000000,"foreign_sell_value":775200000000,"foreign_net_value":216600000000,"foreign_share_bps":8032,"non_regular_bps":null},{"date":"2026-10-15","close":null,"change_bps":null,"volume":150000000,"value":912000000000,"foreign_buy":null,"foreign_sell":null,"foreign_net":null,"foreign_buy_value":826900000000,"foreign_sell_value":540800000000,"foreign_net_value":286100000000,"foreign_share_bps":7498,"non_regular_bps":null},{"date":"2026-10-16","close":null,"change_bps":null,"volume":1260000000,"value":6600000000000,"foreign_buy":null,"foreign_sell":null,"foreign_net":null,"foreign_buy_value":1700000000000,"foreign_sell_value":4000000000000,"foreign_net_value":-2300000000000,"foreign_share_bps":4318,"non_regular_bps":null}],"foreign_net":null,"foreign_buy_value":4364700000000,"foreign_sell_value":6187800000000,"foreign_net_value":-1823100000000,"foreign_share_bps":5310,"rounded":true}
--- stderr

View file

@ -1,4 +1,4 @@
exit: 0
--- stdout
{"symbol":"BBCA.JK","from":"2026-10-12","to":"2026-10-16","days":[{"date":"2026-10-12","close":5950,"change_bps":-165,"volume":97965100,"value":586491892500,"foreign_buy":51499200,"foreign_sell":60883100,"foreign_net":-9383900,"foreign_net_value":-56178999154,"non_regular_bps":14},{"date":"2026-10-13","close":5950,"change_bps":0,"volume":122716900,"value":726575115000,"foreign_buy":88772100,"foreign_sell":83738900,"foreign_net":5033200,"foreign_net_value":29800279088,"non_regular_bps":159},{"date":"2026-10-14","close":6125,"change_bps":294,"volume":136303100,"value":828862772500,"foreign_buy":119876900,"foreign_sell":84298000,"foreign_net":35578900,"foreign_net_value":216356236186,"non_regular_bps":2412},{"date":"2026-10-15","close":6075,"change_bps":-82,"volume":134763200,"value":816554920000,"foreign_buy":120850000,"foreign_sell":73638900,"foreign_net":47211100,"foreign_net_value":286060704877,"non_regular_bps":1046},{"date":"2026-10-16","close":5175,"change_bps":-1481,"volume":1241098200,"value":6474934315000,"foreign_buy":309392800,"foreign_sell":757227900,"foreign_net":-447835100,"foreign_net_value":-2336400823441,"non_regular_bps":152}],"foreign_net":-369395800,"foreign_net_value":-1860362602444,"foreign_share_bps":5050}
{"symbol":"BBCA.JK","source":"ipot","from":"2026-10-12","to":"2026-10-16","days":[{"date":"2026-10-12","close":null,"change_bps":null,"volume":98041500,"value":587100000000,"foreign_buy":null,"foreign_sell":null,"foreign_net":null,"foreign_buy_value":308700000000,"foreign_sell_value":364600000000,"foreign_net_value":-55900000000,"foreign_share_bps":5734,"non_regular_bps":null},{"date":"2026-10-13","close":null,"change_bps":null,"volume":120000000,"value":738200000000,"foreign_buy":null,"foreign_sell":null,"foreign_net":null,"foreign_buy_value":537300000000,"foreign_sell_value":507200000000,"foreign_net_value":30100000000,"foreign_share_bps":7075,"non_regular_bps":null},{"date":"2026-10-14","close":null,"change_bps":null,"volume":180000000,"value":1100000000000,"foreign_buy":null,"foreign_sell":null,"foreign_net":null,"foreign_buy_value":991800000000,"foreign_sell_value":775200000000,"foreign_net_value":216600000000,"foreign_share_bps":8032,"non_regular_bps":null},{"date":"2026-10-15","close":null,"change_bps":null,"volume":150000000,"value":912000000000,"foreign_buy":null,"foreign_sell":null,"foreign_net":null,"foreign_buy_value":826900000000,"foreign_sell_value":540800000000,"foreign_net_value":286100000000,"foreign_share_bps":7498,"non_regular_bps":null},{"date":"2026-10-16","close":null,"change_bps":null,"volume":1260000000,"value":6600000000000,"foreign_buy":null,"foreign_sell":null,"foreign_net":null,"foreign_buy_value":1700000000000,"foreign_sell_value":4000000000000,"foreign_net_value":-2300000000000,"foreign_share_bps":4318,"non_regular_bps":null}],"foreign_net":null,"foreign_buy_value":4364700000000,"foreign_sell_value":6187800000000,"foreign_net_value":-1823100000000,"foreign_share_bps":5310,"rounded":true}
--- stderr

View file

@ -1,19 +1,20 @@
exit: 0
--- stdout
Foreign flow for BBCA.JK, 2026-10-12 to 2026-10-16 (5 sessions)
┌────────────┬───────┬────────┬────────┬─────────────┬──────────────┬─────────────┬───────────┬─────────┐
│ DATE ┆ CLOSE ┆ CHG% ┆ VALUE ┆ FOREIGN BUY ┆ FOREIGN SELL ┆ FOREIGN NET ┆ NET VALUE ┆ NON-REG │
╞════════════╪═══════╪════════╪════════╪═════════════╪══════════════╪═════════════╪═══════════╪═════════╡
│ 2026-10-12 ┆ 5,950 ┆ -1.6% ┆ 586.5B ┆ 51.5M ┆ 60.9M ┆ -9.4M ┆ -56.2B ┆ 0.1% │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
│ 2026-10-13 ┆ 5,950 ┆ +0.0% ┆ 726.6B ┆ 88.8M ┆ 83.7M ┆ +5.0M ┆ +29.8B ┆ 1.6% │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
│ 2026-10-14 ┆ 6,125 ┆ +2.9% ┆ 828.9B ┆ 119.9M ┆ 84.3M ┆ +35.6M ┆ +216.4B ┆ 24.1% │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
│ 2026-10-15 ┆ 6,075 ┆ -0.8% ┆ 816.6B ┆ 120.8M ┆ 73.6M ┆ +47.2M ┆ +286.1B ┆ 10.5% │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
│ 2026-10-16 ┆ 5,175 ┆ -14.8% ┆ 6.5T ┆ 309.4M ┆ 757.2M ┆ -447.8M ┆ -2.3T ┆ 1.5% │
└────────────┴───────┴────────┴────────┴─────────────┴──────────────┴─────────────┴───────────┴─────────┘
Net foreign: -369.4M shares, about -1.9T IDR; foreign investors made up 50.5% of trading.
Volume, value and foreign figures are the regular board, volumes in shares. Net value is estimated at each session's average price; NON-REG is the share of all value traded outside the regular board (negotiated and cash).
Foreign flow for BBCA.JK, 2026-10-12 to 2026-10-16 (5 sessions, IPOT)
┌────────────┬────────┬─────────────┬──────────────┬─────────────┬───────────┐
│ DATE ┆ VALUE ┆ FOREIGN BUY ┆ FOREIGN SELL ┆ FOREIGN NET ┆ FOREIGN % │
╞════════════╪════════╪═════════════╪══════════════╪═════════════╪═══════════╡
│ 2026-10-12 ┆ 587.1B ┆ 308.7B ┆ 364.6B ┆ -55.9B ┆ 57.3% │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
│ 2026-10-13 ┆ 738.2B ┆ 537.3B ┆ 507.2B ┆ +30.1B ┆ 70.8% │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
│ 2026-10-14 ┆ 1.1T ┆ 991.8B ┆ 775.2B ┆ +216.6B ┆ 80.3% │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
│ 2026-10-15 ┆ 912.0B ┆ 826.9B ┆ 540.8B ┆ +286.1B ┆ 75.0% │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
│ 2026-10-16 ┆ 6.6T ┆ 1.7T ┆ 4.0T ┆ -2.3T ┆ 43.2% │
└────────────┴────────┴─────────────┴──────────────┴─────────────┴───────────┘
Foreign investors bought 4.4T IDR and sold 6.2T IDR: net -1.8T; they made up 53.1% of trading.
Values in IDR across all boards. For exact share counts, the close and the non-regular share, use --source idx (needs curl-impersonate).
Note: the source rounds figures of a million and more to one decimal.
--- stderr

View file

@ -167,7 +167,11 @@ impl Broker<'_> {
/// trades the lists show. `F. NVal`, the foreign net, ignores both filters:
/// on all boards it gives the side's selling (foreign buying less the foreign
/// net; domestic buying plus it), and with a board filter it is dropped.
fn summary_from_raw(symbol: &str, query: &BrokerQuery, raw: BrokerSummaryRaw) -> BrokerSummary {
pub(crate) fn summary_from_raw(
symbol: &str,
query: &BrokerQuery,
raw: BrokerSummaryRaw,
) -> BrokerSummary {
let one_side = query.investor != BrokerInvestor::All;
let all_boards = query.board == BrokerBoard::All;
let foreign_net = raw.foreign_net_value.filter(|_| all_boards);

View file

@ -1,18 +1,19 @@
//! Foreign flow: what foreign investors bought and sold of a stock, session
//! by session, from IDX's daily trading summary (end of day). One request
//! covers every stock for one session and is cached by date.
//! by session (end of day). IPOT (the default) needs two plain requests per
//! session; IDX's trading summary covers every stock in one request per
//! session, cached by date, but needs curl-impersonate.
pub mod types;
use chrono::{DateTime, Datelike, Duration, NaiveDate, Utc, Weekday};
use crate::api::{today_wib, trading_summary_provider};
use crate::broker::types::{BrokerBoard, BrokerInvestor, BrokerQuery, BrokerSummary};
use crate::client::{CachePolicy, CacheSpec, Fetched, Idx, cached_fetch};
use crate::error::IdxError;
use types::{FlowRange, ForeignFlow, ForeignFlowDay, StockDay};
use types::{FlowRange, FlowSource, ForeignFlow, ForeignFlowDay, StockDay};
const SOURCE: &str = "idx";
/// Most sessions `FlowRange::Latest` reads, one request each.
/// Most sessions `FlowRange::Latest` reads.
pub const MAX_SESSIONS: usize = 60;
/// Longest `FlowRange::Dates` range, calendar days.
pub const MAX_RANGE_DAYS: i64 = 92;
@ -24,17 +25,36 @@ pub struct Flow<'a> {
pub(crate) idx: &'a Idx,
}
/// What one weekday held for the stock.
enum Day {
/// The market (or, for IPOT, the stock) did not trade.
NoSession,
/// The market traded without the stock (a suspension).
Absent,
Traded {
day: Box<ForeignFlowDay>,
rounded: bool,
},
}
struct Fetch {
day: Day,
fetched_at: DateTime<Utc>,
stale: bool,
}
impl Flow<'_> {
/// Today in WIB.
pub fn today(&self) -> NaiveDate {
today_wib()
}
/// Foreign buying and selling of `symbol` over `range`.
/// Foreign buying and selling of `symbol` over `range`, from `source`.
pub fn foreign(
&self,
symbol: &str,
range: FlowRange,
source: FlowSource,
policy: CachePolicy,
) -> Result<Fetched<ForeignFlow>, IdxError> {
let (resolved, code) = self.idx.idx_code(symbol, "foreign flow")?;
@ -63,8 +83,9 @@ impl Flow<'_> {
}
};
let mut rows = Vec::new();
let mut days = Vec::new();
let mut sessions = 0;
let mut rounded = false;
let mut fetched_at: Option<DateTime<Utc>> = None;
let mut stale = false;
for date in dates {
@ -74,39 +95,117 @@ impl Flow<'_> {
if matches!(date.weekday(), Weekday::Sat | Weekday::Sun) {
continue;
}
let day = match self.session(date, today, policy) {
Ok(day) => day,
let fetch = match source {
FlowSource::Ipot => self.ipot_day(&resolved, date, policy),
FlowSource::Idx => self.idx_day(&code, date, today, policy),
};
let fetch = match fetch {
Ok(fetch) => fetch,
Err(IdxError::CacheMiss(_)) if skip_misses => continue,
Err(err) => return Err(err),
};
if day.data.is_empty() {
continue;
match fetch.day {
Day::NoSession => continue,
Day::Absent => {}
Day::Traded {
day,
rounded: day_rounded,
} => {
days.push(*day);
rounded |= day_rounded;
}
}
sessions += 1;
fetched_at = Some(fetched_at.map_or(day.fetched_at, |at| at.min(day.fetched_at)));
stale |= day.stale;
// A stock missing from a session did not trade (suspension).
if let Some(row) = day.data.into_iter().find(|row| row.code == code) {
rows.push(row);
}
fetched_at = Some(fetched_at.map_or(fetch.fetched_at, |at| at.min(fetch.fetched_at)));
stale |= fetch.stale;
}
if sessions == 0 {
return Err(if policy == CachePolicy::Offline {
IdxError::CacheMiss(format!("idx-stocksummary/{resolved} (no cached session)"))
IdxError::CacheMiss(format!(
"{}-flow/{resolved} (no cached session)",
source.as_str()
))
} else {
IdxError::NoMarketData(format!("{resolved}: no IDX sessions in range"))
IdxError::NoMarketData(format!("{resolved}: no sessions in range"))
});
}
rows.reverse();
let flow = foreign_flow(&resolved, &rows).ok_or(IdxError::SymbolNotFound(code))?;
days.reverse();
let flow =
foreign_flow(&resolved, source, days, rounded).ok_or(IdxError::SymbolNotFound(code))?;
Ok(Fetched {
data: flow,
source: SOURCE,
source: source.as_str(),
fetched_at: fetched_at.unwrap_or_else(Utc::now),
stale,
})
}
/// One session from IPOT: the all-investor summary for the value traded,
/// the foreign one for foreign buying, selling and net. Both go through
/// the broker summary cache.
fn ipot_day(
&self,
resolved: &str,
date: NaiveDate,
policy: CachePolicy,
) -> Result<Fetch, IdxError> {
let broker = self.idx.broker();
let all_query = BrokerQuery {
from: date,
to: date,
investor: BrokerInvestor::All,
board: BrokerBoard::All,
};
let all = broker.summary(resolved, &all_query, policy)?;
if all.data.is_empty() {
return Ok(Fetch {
day: Day::NoSession,
fetched_at: all.fetched_at,
stale: all.stale,
});
}
let foreign_query = BrokerQuery {
investor: BrokerInvestor::Foreign,
..all_query
};
let foreign = broker.summary(resolved, &foreign_query, policy)?;
Ok(Fetch {
day: Day::Traded {
day: Box::new(ipot_flow_day(date, &all.data, &foreign.data)),
rounded: all.data.rounded || foreign.data.rounded,
},
fetched_at: all.fetched_at.min(foreign.fetched_at),
stale: all.stale || foreign.stale,
})
}
/// One session from IDX's trading summary of every stock.
fn idx_day(
&self,
code: &str,
date: NaiveDate,
today: NaiveDate,
policy: CachePolicy,
) -> Result<Fetch, IdxError> {
let session = self.session(date, today, policy)?;
let day = if session.data.is_empty() {
Day::NoSession
} else {
match session.data.iter().find(|row| row.code == code) {
Some(row) => Day::Traded {
day: Box::new(idx_flow_day(row)),
rounded: false,
},
None => Day::Absent,
}
};
Ok(Fetch {
day,
fetched_at: session.fetched_at,
stale: session.stale,
})
}
/// Every stock's summary for one session; empty without trading.
fn session(
&self,
@ -127,7 +226,7 @@ impl Flow<'_> {
TTL_TODAY_SECS
},
subject: &key,
source: SOURCE,
source: FlowSource::Idx.as_str(),
},
policy,
|| provider.stock_summary(date),
@ -160,17 +259,54 @@ fn bps(part: i64, whole: i64) -> Option<i64> {
(whole > 0).then(|| (part as f64 * 10_000.0 / whole as f64).round() as i64)
}
fn flow_day(row: &StockDay) -> ForeignFlowDay {
/// `shares` at the session's average price (value over volume), whole IDR.
fn at_average(shares: Option<i64>, value: Option<i64>, volume: Option<i64>) -> Option<i64> {
match (shares, value, volume) {
(Some(shares), Some(value), Some(volume)) if volume > 0 => {
i64::try_from(i128::from(shares) * i128::from(value) / i128::from(volume)).ok()
}
_ => None,
}
}
/// Foreign buying plus selling over twice `total`.
fn share(buy: Option<i64>, sell: Option<i64>, total: Option<i64>) -> Option<i64> {
let traded = buy?.checked_add(sell?)?;
bps(traded, total?.checked_mul(2)?)
}
/// One IPOT session: the value traded from the all-investor summary, the
/// foreign figures from the foreign one (`buy_value` is its footer,
/// `sell_value` that less the foreign net).
pub fn ipot_flow_day(
date: NaiveDate,
all: &BrokerSummary,
foreign: &BrokerSummary,
) -> ForeignFlowDay {
let foreign_net_value = foreign.foreign_net_value.or(all.foreign_net_value);
ForeignFlowDay {
date,
close: None,
change_bps: None,
volume: all.total_lots.and_then(|lots| lots.checked_mul(100)),
value: all.total_value,
foreign_buy: None,
foreign_sell: None,
foreign_net: None,
foreign_buy_value: foreign.buy_value,
foreign_sell_value: foreign.sell_value,
foreign_net_value,
foreign_share_bps: share(foreign.buy_value, foreign.sell_value, all.total_value),
non_regular_bps: None,
}
}
/// One IDX session for one stock.
pub fn idx_flow_day(row: &StockDay) -> ForeignFlowDay {
let foreign_net = row
.foreign_buy
.zip(row.foreign_sell)
.map(|(buy, sell)| buy - sell);
let foreign_net_value = match (foreign_net, row.value, row.volume) {
(Some(net), Some(value), Some(volume)) if volume > 0 => {
i64::try_from(i128::from(net) * i128::from(value) / i128::from(volume)).ok()
}
_ => None,
};
ForeignFlowDay {
date: row.date,
close: row.close,
@ -183,7 +319,10 @@ fn flow_day(row: &StockDay) -> ForeignFlowDay {
foreign_buy: row.foreign_buy,
foreign_sell: row.foreign_sell,
foreign_net,
foreign_net_value,
foreign_buy_value: at_average(row.foreign_buy, row.value, row.volume),
foreign_sell_value: at_average(row.foreign_sell, row.value, row.volume),
foreign_net_value: at_average(foreign_net, row.value, row.volume),
foreign_share_bps: share(row.foreign_buy, row.foreign_sell, row.volume),
// `value` is the regular board only; the share is of all trading.
non_regular_bps: row
.non_regular_value
@ -197,25 +336,38 @@ fn sum(days: &[ForeignFlowDay], field: fn(&ForeignFlowDay) -> Option<i64>) -> Op
.try_fold(0_i64, |total, day| total.checked_add(field(day)?))
}
/// The flow over `rows` (one stock, oldest first); `None` without rows.
pub fn foreign_flow(symbol: &str, rows: &[StockDay]) -> Option<ForeignFlow> {
let (first, last) = (rows.first()?, rows.last()?);
let days: Vec<ForeignFlowDay> = rows.iter().map(flow_day).collect();
let foreign_share_bps = match (
/// The flow over `days` (one stock, oldest first); `None` without days.
pub fn foreign_flow(
symbol: &str,
source: FlowSource,
days: Vec<ForeignFlowDay>,
rounded: bool,
) -> Option<ForeignFlow> {
let (from, to) = (days.first()?.date, days.last()?.date);
// IDX has exact shares; IPOT has values.
let foreign_share_bps = match source {
FlowSource::Idx => share(
sum(&days, |day| day.foreign_buy),
sum(&days, |day| day.foreign_sell),
sum(&days, |day| day.volume),
) {
(Some(buy), Some(sell), Some(volume)) => bps(buy + sell, 2 * volume),
_ => None,
),
FlowSource::Ipot => share(
sum(&days, |day| day.foreign_buy_value),
sum(&days, |day| day.foreign_sell_value),
sum(&days, |day| day.value),
),
};
Some(ForeignFlow {
symbol: symbol.to_string(),
from: first.date,
to: last.date,
source,
from,
to,
foreign_net: sum(&days, |day| day.foreign_net),
foreign_buy_value: sum(&days, |day| day.foreign_buy_value),
foreign_sell_value: sum(&days, |day| day.foreign_sell_value),
foreign_net_value: sum(&days, |day| day.foreign_net_value),
foreign_share_bps,
rounded,
days,
})
}
@ -242,13 +394,15 @@ mod tests {
}
}
fn idx_flow(rows: &[StockDay]) -> Option<ForeignFlow> {
let days = rows.iter().map(idx_flow_day).collect();
foreign_flow("TEST.JK", FlowSource::Idx, days, false)
}
#[test]
fn test_foreign_flow_sums_sessions_and_estimates_value() {
let flow = foreign_flow(
"TEST.JK",
&[row(15, 1100, 600, Some(200)), row(16, 950, 100, Some(300))],
)
.unwrap();
let flow =
idx_flow(&[row(15, 1100, 600, Some(200)), row(16, 950, 100, Some(300))]).unwrap();
let first = &flow.days[0];
assert_eq!(
(first.foreign_net, first.change_bps),
@ -256,6 +410,11 @@ mod tests {
);
// 400 shares at 1,100 (value over volume).
assert_eq!(first.foreign_net_value, Some(440_000));
assert_eq!(
(first.foreign_buy_value, first.foreign_sell_value),
(Some(660_000), Some(220_000))
);
assert_eq!(first.foreign_share_bps, Some(4000));
// 110,000 of 1,100,000 regular plus 110,000 non-regular.
assert_eq!(first.non_regular_bps, Some(909));
assert_eq!(flow.days[1].change_bps, Some(-500));
@ -270,14 +429,10 @@ mod tests {
#[test]
fn test_foreign_flow_never_counts_a_missing_figure_as_zero() {
let flow = foreign_flow(
"TEST.JK",
&[row(15, 1100, 600, Some(200)), row(16, 950, 100, None)],
)
.unwrap();
let flow = idx_flow(&[row(15, 1100, 600, Some(200)), row(16, 950, 100, None)]).unwrap();
assert_eq!(flow.days[1].foreign_net, None);
assert_eq!((flow.foreign_net, flow.foreign_share_bps), (None, None));
assert!(foreign_flow("TEST.JK", &[]).is_none());
assert!(idx_flow(&[]).is_none());
}
#[test]
@ -286,11 +441,50 @@ mod tests {
let date = NaiveDate::from_ymd_opt(2026, 10, 14).unwrap();
let rows = crate::api::idx::parse_stock_summary(raw, date).unwrap();
let bbri: Vec<StockDay> = rows.into_iter().filter(|row| row.code == "BBRI").collect();
let flow = foreign_flow("BBRI.JK", &bbri).unwrap();
let flow = idx_flow(&bbri).unwrap();
// 172.3B non-regular against 338.5B regular: 33.74%, not 50.91%.
assert_eq!(flow.days[0].non_regular_bps, Some(3374));
}
#[test]
fn test_ipot_flow_day_reads_foreign_buying_and_selling_from_the_summaries() {
let day = NaiveDate::from_ymd_opt(2026, 10, 16).unwrap();
let summary = |investor, name: &str| {
let html = std::fs::read_to_string(format!("tests/fixtures/{name}")).unwrap();
let raw = crate::api::ipot::summary_from_html("BBCA", &html).unwrap();
let query = BrokerQuery {
from: day,
to: day,
investor,
board: BrokerBoard::All,
};
crate::broker::summary_from_raw("BBCA.JK", &query, raw)
};
let all = summary(BrokerInvestor::All, "ipot_brokersummary_bbca_20261016.html");
let foreign = summary(
BrokerInvestor::Foreign,
"ipot_brokersummary_bbca_20261016_foreign_all.html",
);
let flow = ipot_flow_day(day, &all, &foreign);
assert_eq!(flow.value, Some(6_600_000_000_000));
assert_eq!(flow.volume, Some(1_260_000_000));
assert_eq!(
(
flow.foreign_buy_value,
flow.foreign_sell_value,
flow.foreign_net_value
),
(
Some(1_700_000_000_000),
Some(4_000_000_000_000),
Some(-2_300_000_000_000)
)
);
// (1.7 T + 4.0 T) / (2 * 6.6 T).
assert_eq!(flow.foreign_share_bps, Some(4318));
assert_eq!((flow.close, flow.foreign_net), (None, None));
}
#[test]
fn test_check_range_bounds() {
let day = |d| NaiveDate::from_ymd_opt(2026, 10, d).unwrap();

View file

@ -1,5 +1,7 @@
//! Foreign flow types: what foreign investors bought and sold of a stock,
//! session by session (IDX trading summary, end of day).
//! session by session (end of day), from IPOT or IDX's trading summary.
use std::str::FromStr;
use chrono::NaiveDate;
use serde::{Deserialize, Serialize};
@ -9,6 +11,47 @@ use crate::error::IdxError;
/// Sessions shown when a frontend gives neither days nor dates.
pub const DEFAULT_SESSIONS: usize = 10;
/// Where a foreign flow comes from.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
#[serde(rename_all = "snake_case")]
pub enum FlowSource {
/// Indo Premier's broker summary: foreign buy, sell and net value across
/// all boards, two plain requests per session. Figures of a million and
/// more are rounded to one decimal.
#[default]
Ipot,
/// IDX's own trading summary: exact foreign share counts, close and the
/// non-regular share, regular board. Needs curl-impersonate and a
/// network IDX's Cloudflare lets through.
Idx,
}
impl FlowSource {
pub const ALL: [Self; 2] = [Self::Ipot, Self::Idx];
pub fn as_str(self) -> &'static str {
match self {
Self::Ipot => "ipot",
Self::Idx => "idx",
}
}
}
impl FromStr for FlowSource {
type Err = IdxError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value.trim().to_ascii_lowercase().as_str() {
"ipot" => Ok(Self::Ipot),
"idx" => Ok(Self::Idx),
_ => Err(IdxError::InvalidInput(format!(
"invalid source '{value}'; use ipot or idx"
))),
}
}
}
/// One stock's trading summary for one session, as IDX publishes it.
/// Prices are whole IDR, volumes are shares. Volume, value and the foreign
/// figures cover the regular board only (across a week of every stock, no
@ -77,27 +120,41 @@ impl FlowRange {
}
}
/// One session. Which figures are present depends on the source: IPOT gives
/// values across all boards, IDX gives exact shares on the regular board (and
/// values estimated from them) with the close and the non-regular share.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ForeignFlowDay {
pub date: NaiveDate,
/// Close, whole IDR (IDX only).
pub close: Option<i64>,
/// Close against the previous close, basis points.
/// Close against the previous close, basis points (IDX only).
pub change_bps: Option<i64>,
/// Shares traded on the regular board.
/// Shares traded: the regular board (IDX), or all boards from lots
/// (IPOT).
pub volume: Option<i64>,
/// Value traded on the regular board, whole IDR.
/// Value traded, whole IDR: the regular board (IDX) or all boards (IPOT).
pub value: Option<i64>,
/// Shares foreign investors bought (regular board).
/// Shares foreign investors bought (IDX only).
pub foreign_buy: Option<i64>,
/// Shares foreign investors sold.
/// Shares foreign investors sold (IDX only).
pub foreign_sell: Option<i64>,
/// `foreign_buy - foreign_sell`, shares.
/// `foreign_buy - foreign_sell`, shares (IDX only).
pub foreign_net: Option<i64>,
/// `foreign_net` at the session's average price (value over volume),
/// whole IDR: an estimate.
/// Value foreign investors bought, whole IDR: as published (IPOT), or
/// estimated at the session's average price (IDX).
pub foreign_buy_value: Option<i64>,
/// Value foreign investors sold, whole IDR: their buying less their net
/// (IPOT), or estimated at the session's average price (IDX).
pub foreign_sell_value: Option<i64>,
/// Foreign buy value minus sell value, whole IDR: as published (IPOT), or
/// net shares at the session's average price (IDX, an estimate).
pub foreign_net_value: Option<i64>,
/// Foreign buying and selling over twice the session's trading: foreign
/// investors' share of it, basis points.
pub foreign_share_bps: Option<i64>,
/// Value traded outside the regular board (negotiated and cash), basis
/// points of all value traded (regular plus non-regular).
/// points of all value traded (IDX only).
pub non_regular_bps: Option<i64>,
}
@ -105,16 +162,23 @@ pub struct ForeignFlowDay {
pub struct ForeignFlow {
/// Resolved ticker, e.g. `BBCA.JK`.
pub symbol: String,
pub source: FlowSource,
/// First and last session covered.
pub from: NaiveDate,
pub to: NaiveDate,
/// Sessions the stock traded, oldest first.
pub days: Vec<ForeignFlowDay>,
/// Net shares over the sessions; `None` when a session lacks a figure.
/// Net shares over the sessions (IDX only); `None` when a session lacks
/// a figure.
pub foreign_net: Option<i64>,
/// Sum of the sessions' estimated net values, whole IDR.
/// Foreign buy value over the sessions, whole IDR.
pub foreign_buy_value: Option<i64>,
/// Foreign sell value over the sessions, whole IDR.
pub foreign_sell_value: Option<i64>,
/// Foreign net value over the sessions, whole IDR.
pub foreign_net_value: Option<i64>,
/// Foreign buys and sells over twice the volume: foreign investors'
/// share of regular-board trading, basis points.
/// Foreign investors' share of trading over the sessions, basis points.
pub foreign_share_bps: Option<i64>,
/// The source rounded figures of a million and more to one decimal.
pub rounded: bool,
}

View file

@ -0,0 +1,181 @@
<div id="modContentBrokerSum"></div>
<!--DATA BROKER SUMMARY-->
<div class="pa5">
<div class="row mincol-3">
<div class="col col-xs-5 col-sm-3">
<input class="form-control input-xs text-center" id="startDateBrokerSummary" name="startDate" placeholder="DD Month YYYY" value="10/12/2026" type="text">
</div>
<div class="col col-xs-5 col-sm-3">
<input class="form-control input-xs text-center" id="endDateBrokerSummary" name="endDate" placeholder="DD Month YYYY" value="10/12/2026" type="text">
</div>
<div class="mt15 visible-xs">&nbsp;</div>
<div class="col col-xs-5 col-sm-3">
<select class="form-control input-xs" id="FDBrokerSummary">
<option value="all">All</option>
<option value="F" >Foreign</option>
<option value="D" >Domestic</option>
</select>
</div>
<div class="col col-xs-5 col-sm-2">
<select class="form-control input-xs" id="boardBrokerSummary">
<option value="all">All Trade</option>
<option value="RG" >Regular</option>
<option value="TN" >Tunai</option>
<option value="NG" >Nego</option>
</select>
</div>
<div class="col col-xs-2 col-sm-1">
<button type="button" class="btn btn-default btn-block btn-xs-input" onClick="quickinfo_brokersummary($('#stockPageCode').val()!==undefined?$('#stockPageCode').val():$('#stockInfoCode').val(),$('#startDateBrokerSummary').val(),$('#endDateBrokerSummary').val(),$('#FDBrokerSummary').val(),$('#boardBrokerSummary').val());">GO</button>
</div>
</div>
</div>
<div class="table-responsive">
<table width="100%" border="0" cellspacing="0" cellpadding="0" class="table table-summary table-hover noborder nm">
<thead>
<tr>
<th align="center">Buyer</th>
<th align="right">B.Lot</th>
<th align="right">B.Val</th>
<th align="right">B.Avg</th>
<th align="center">#</th>
<th align="center">Seller</th>
<th align="right">S.Lot</th>
<th align="right">S.Val</th>
<th align="right">S.Avg</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><span class="text-foreign">ZP</span></td>
<td align="right"><span class="text-up">319,263 </span></td>
<td align="right"><span class="text-up">191.3 B</span></td>
<td align="right"><span class="text-up">5,993 </span></td>
<td align="center">1</td>
<td align="center"><span class="text-bumn">CC</span></td>
<td align="right"><span class="text-down">248,149 </span></td>
<td align="right"><span class="text-down">148.6 B</span></td>
<td align="right"><span class="text-down">5,988 </span></td>
</tr>
<tr>
<td align="center"><span class="text-bumn">CC</span></td>
<td align="right"><span class="text-up">91,178 </span></td>
<td align="right"><span class="text-up">54.7 B</span></td>
<td align="right"><span class="text-up">5,998 </span></td>
<td align="center">2</td>
<td align="center"><span class="text-foreign">KZ</span></td>
<td align="right"><span class="text-down">169,689 </span></td>
<td align="right"><span class="text-down">101.8 B</span></td>
<td align="right"><span class="text-down">6,000 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">RX</span></td>
<td align="right"><span class="text-up">82,364 </span></td>
<td align="right"><span class="text-up">49.3 B</span></td>
<td align="right"><span class="text-up">5,991 </span></td>
<td align="center">3</td>
<td align="center"><span class="text-foreign">ZP</span></td>
<td align="right"><span class="text-down">142,270 </span></td>
<td align="right"><span class="text-down">85.1 B</span></td>
<td align="right"><span class="text-down">5,980 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">SQ</span></td>
<td align="right"><span class="text-up">70,119 </span></td>
<td align="right"><span class="text-up">41.9 B</span></td>
<td align="right"><span class="text-up">5,973 </span></td>
<td align="center">4</td>
<td align="center"><span class="text-foreign">AK</span></td>
<td align="right"><span class="text-down">109,896 </span></td>
<td align="right"><span class="text-down">65.7 B</span></td>
<td align="right"><span class="text-down">5,978 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">XL</span></td>
<td align="right"><span class="text-up">66,085 </span></td>
<td align="right"><span class="text-up">39.5 B</span></td>
<td align="right"><span class="text-up">5,982 </span></td>
<td align="center">5</td>
<td align="center"><span class="text-foreign">YU</span></td>
<td align="right"><span class="text-down">51,291 </span></td>
<td align="right"><span class="text-down">30.7 B</span></td>
<td align="right"><span class="text-down">5,977 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">BB</span></td>
<td align="right"><span class="text-up">46,535 </span></td>
<td align="right"><span class="text-up">27.9 B</span></td>
<td align="right"><span class="text-up">5,989 </span></td>
<td align="center">6</td>
<td align="center"><span class="text-foreign">BK</span></td>
<td align="right"><span class="text-down">35,962 </span></td>
<td align="right"><span class="text-down">21.5 B</span></td>
<td align="right"><span class="text-down">5,988 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">YP</span></td>
<td align="right"><span class="text-up">31,891 </span></td>
<td align="right"><span class="text-up">19.1 B</span></td>
<td align="right"><span class="text-up">5,983 </span></td>
<td align="center">7</td>
<td align="center"><span class="text-local">XL</span></td>
<td align="right"><span class="text-down">33,257 </span></td>
<td align="right"><span class="text-down">19.9 B</span></td>
<td align="right"><span class="text-down">5,987 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">XC</span></td>
<td align="right"><span class="text-up">31,403 </span></td>
<td align="right"><span class="text-up">18.8 B</span></td>
<td align="right"><span class="text-up">5,983 </span></td>
<td align="center">8</td>
<td align="center"><span class="text-local">CP</span></td>
<td align="right"><span class="text-down">29,928 </span></td>
<td align="right"><span class="text-down">17.8 B</span></td>
<td align="right"><span class="text-down">5,962 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">AK</span></td>
<td align="right"><span class="text-up">24,560 </span></td>
<td align="right"><span class="text-up">14.7 B</span></td>
<td align="right"><span class="text-up">5,967 </span></td>
<td align="center">9</td>
<td align="center"><span class="text-local">PD</span></td>
<td align="right"><span class="text-down">29,063 </span></td>
<td align="right"><span class="text-down">17.5 B</span></td>
<td align="right"><span class="text-down">6,023 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">BK</span></td>
<td align="right"><span class="text-up">20,056 </span></td>
<td align="right"><span class="text-up">12.0 B</span></td>
<td align="right"><span class="text-up">5,970 </span></td>
<td align="center">10</td>
<td align="center"><span class="text-local">SQ</span></td>
<td align="right"><span class="text-down">23,464 </span></td>
<td align="right"><span class="text-down">14.1 B</span></td>
<td align="right"><span class="text-down">6,002 </span></td>
</tr>
</tbody>
<tfoot>
<tr class="bdr-t">
<th colspan="9" align="center">
<div class="text-center pt20 valign-middle" style="height:50px;">
<span class="fsize12 ml5 mr5">T. Val : 587.1 B</span>
<span class="fsize12 ml5 mr5">F. NVal : -55.9 B</span>
<span class="fsize12 ml5 mr5">T.Lot : 980,415 </span>
<span class="fsize12 ml5 mr5">Avg : 5,988 </span>
</div>
</th>
</tr>
</tfoot>
</table>
</div>
<!-- =======================
======SCRIPT MODULE=========
============================-->
<script src="../../../../module/saham/include/js/data-brokersummary.js"></script>

View file

@ -0,0 +1,181 @@
<div id="modContentBrokerSum"></div>
<!--DATA BROKER SUMMARY-->
<div class="pa5">
<div class="row mincol-3">
<div class="col col-xs-5 col-sm-3">
<input class="form-control input-xs text-center" id="startDateBrokerSummary" name="startDate" placeholder="DD Month YYYY" value="10/12/2026" type="text">
</div>
<div class="col col-xs-5 col-sm-3">
<input class="form-control input-xs text-center" id="endDateBrokerSummary" name="endDate" placeholder="DD Month YYYY" value="10/12/2026" type="text">
</div>
<div class="mt15 visible-xs">&nbsp;</div>
<div class="col col-xs-5 col-sm-3">
<select class="form-control input-xs" id="FDBrokerSummary">
<option value="all">All</option>
<option value="F" selected>Foreign</option>
<option value="D" >Domestic</option>
</select>
</div>
<div class="col col-xs-5 col-sm-2">
<select class="form-control input-xs" id="boardBrokerSummary">
<option value="all">All Trade</option>
<option value="RG" >Regular</option>
<option value="TN" >Tunai</option>
<option value="NG" >Nego</option>
</select>
</div>
<div class="col col-xs-2 col-sm-1">
<button type="button" class="btn btn-default btn-block btn-xs-input" onClick="quickinfo_brokersummary($('#stockPageCode').val()!==undefined?$('#stockPageCode').val():$('#stockInfoCode').val(),$('#startDateBrokerSummary').val(),$('#endDateBrokerSummary').val(),$('#FDBrokerSummary').val(),$('#boardBrokerSummary').val());">GO</button>
</div>
</div>
</div>
<div class="table-responsive">
<table width="100%" border="0" cellspacing="0" cellpadding="0" class="table table-summary table-hover noborder nm">
<thead>
<tr>
<th align="center">Buyer</th>
<th align="right">B.Lot</th>
<th align="right">B.Val</th>
<th align="right">B.Avg</th>
<th align="center">#</th>
<th align="center">Seller</th>
<th align="right">S.Lot</th>
<th align="right">S.Val</th>
<th align="right">S.Avg</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><span class="text-foreign">ZP</span></td>
<td align="right"><span class="text-up">316,144 </span></td>
<td align="right"><span class="text-up">189.5 B</span></td>
<td align="right"><span class="text-up">5,993 </span></td>
<td align="center">1</td>
<td align="center"><span class="text-bumn">CC</span></td>
<td align="right"><span class="text-down">201,030 </span></td>
<td align="right"><span class="text-down">120.4 B</span></td>
<td align="right"><span class="text-down">5,992 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">RX</span></td>
<td align="right"><span class="text-up">82,364 </span></td>
<td align="right"><span class="text-up">49.3 B</span></td>
<td align="right"><span class="text-up">5,991 </span></td>
<td align="center">2</td>
<td align="center"><span class="text-foreign">ZP</span></td>
<td align="right"><span class="text-down">137,297 </span></td>
<td align="right"><span class="text-down">82.1 B</span></td>
<td align="right"><span class="text-down">5,981 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">BB</span></td>
<td align="right"><span class="text-up">25,000 </span></td>
<td align="right"><span class="text-up">15.0 B</span></td>
<td align="right"><span class="text-up">5,989 </span></td>
<td align="center">3</td>
<td align="center"><span class="text-foreign">KZ</span></td>
<td align="right"><span class="text-down">123,872 </span></td>
<td align="right"><span class="text-down">74.4 B</span></td>
<td align="right"><span class="text-down">6,007 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">AK</span></td>
<td align="right"><span class="text-up">24,560 </span></td>
<td align="right"><span class="text-up">14.7 B</span></td>
<td align="right"><span class="text-up">5,967 </span></td>
<td align="center">4</td>
<td align="center"><span class="text-foreign">AK</span></td>
<td align="right"><span class="text-down">89,396 </span></td>
<td align="right"><span class="text-down">53.5 B</span></td>
<td align="right"><span class="text-down">5,980 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">BK</span></td>
<td align="right"><span class="text-up">20,056 </span></td>
<td align="right"><span class="text-up">12.0 B</span></td>
<td align="right"><span class="text-up">5,970 </span></td>
<td align="center">5</td>
<td align="center"><span class="text-foreign">BK</span></td>
<td align="right"><span class="text-down">25,685 </span></td>
<td align="right"><span class="text-down">15.4 B</span></td>
<td align="right"><span class="text-down">5,985 </span></td>
</tr>
<tr>
<td align="center"><span class="text-bumn">CC</span></td>
<td align="right"><span class="text-up">19,945 </span></td>
<td align="right"><span class="text-up">12.1 B</span></td>
<td align="right"><span class="text-up">6,069 </span></td>
<td align="center">6</td>
<td align="center"><span class="text-foreign">RX</span></td>
<td align="right"><span class="text-down">20,942 </span></td>
<td align="right"><span class="text-down">12.5 B</span></td>
<td align="right"><span class="text-down">5,950 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">KZ</span></td>
<td align="right"><span class="text-up">8,553 </span></td>
<td align="right"><span class="text-up">5.2 B</span></td>
<td align="right"><span class="text-up">6,075 </span></td>
<td align="center">7</td>
<td align="center"><span class="text-foreign">YU</span></td>
<td align="right"><span class="text-down">7,476 </span></td>
<td align="right"><span class="text-down">4.5 B</span></td>
<td align="right"><span class="text-down">5,989 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">BQ</span></td>
<td align="right"><span class="text-up">5,000 </span></td>
<td align="right"><span class="text-up">3.0 B</span></td>
<td align="right"><span class="text-up">5,950 </span></td>
<td align="center">8</td>
<td align="center"><span class="text-local">HD</span></td>
<td align="right"><span class="text-down">1,084 </span></td>
<td align="right"><span class="text-down">647.7 M</span></td>
<td align="right"><span class="text-down">5,975 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">AG</span></td>
<td align="right"><span class="text-up">4,315 </span></td>
<td align="right"><span class="text-up">2.6 B</span></td>
<td align="right"><span class="text-up">5,951 </span></td>
<td align="center">9</td>
<td align="center"><span class="text-local">BB</span></td>
<td align="right"><span class="text-down">782 </span></td>
<td align="right"><span class="text-down">465.3 M</span></td>
<td align="right"><span class="text-down">5,950 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">YU</span></td>
<td align="right"><span class="text-up">3,957 </span></td>
<td align="right"><span class="text-up">2.4 B</span></td>
<td align="right"><span class="text-up">5,979 </span></td>
<td align="center">10</td>
<td align="center"><span class="text-foreign">AG</span></td>
<td align="right"><span class="text-down">696 </span></td>
<td align="right"><span class="text-down">417.3 M</span></td>
<td align="right"><span class="text-down">5,995 </span></td>
</tr>
</tbody>
<tfoot>
<tr class="bdr-t">
<th colspan="9" align="center">
<div class="text-center pt20 valign-middle" style="height:50px;">
<span class="fsize12 ml5 mr5">T. Val : 308.7 B</span>
<span class="fsize12 ml5 mr5">F. NVal : -55.9 B</span>
<span class="fsize12 ml5 mr5">T.Lot : 514,995 </span>
<span class="fsize12 ml5 mr5">Avg : 5,994 </span>
</div>
</th>
</tr>
</tfoot>
</table>
</div>
<!-- =======================
======SCRIPT MODULE=========
============================-->
<script src="../../../../module/saham/include/js/data-brokersummary.js"></script>

View file

@ -0,0 +1,181 @@
<div id="modContentBrokerSum"></div>
<!--DATA BROKER SUMMARY-->
<div class="pa5">
<div class="row mincol-3">
<div class="col col-xs-5 col-sm-3">
<input class="form-control input-xs text-center" id="startDateBrokerSummary" name="startDate" placeholder="DD Month YYYY" value="10/13/2026" type="text">
</div>
<div class="col col-xs-5 col-sm-3">
<input class="form-control input-xs text-center" id="endDateBrokerSummary" name="endDate" placeholder="DD Month YYYY" value="10/13/2026" type="text">
</div>
<div class="mt15 visible-xs">&nbsp;</div>
<div class="col col-xs-5 col-sm-3">
<select class="form-control input-xs" id="FDBrokerSummary">
<option value="all">All</option>
<option value="F" >Foreign</option>
<option value="D" >Domestic</option>
</select>
</div>
<div class="col col-xs-5 col-sm-2">
<select class="form-control input-xs" id="boardBrokerSummary">
<option value="all">All Trade</option>
<option value="RG" >Regular</option>
<option value="TN" >Tunai</option>
<option value="NG" >Nego</option>
</select>
</div>
<div class="col col-xs-2 col-sm-1">
<button type="button" class="btn btn-default btn-block btn-xs-input" onClick="quickinfo_brokersummary($('#stockPageCode').val()!==undefined?$('#stockPageCode').val():$('#stockInfoCode').val(),$('#startDateBrokerSummary').val(),$('#endDateBrokerSummary').val(),$('#FDBrokerSummary').val(),$('#boardBrokerSummary').val());">GO</button>
</div>
</div>
</div>
<div class="table-responsive">
<table width="100%" border="0" cellspacing="0" cellpadding="0" class="table table-summary table-hover noborder nm">
<thead>
<tr>
<th align="center">Buyer</th>
<th align="right">B.Lot</th>
<th align="right">B.Val</th>
<th align="right">B.Avg</th>
<th align="center">#</th>
<th align="center">Seller</th>
<th align="right">S.Lot</th>
<th align="right">S.Val</th>
<th align="right">S.Avg</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><span class="text-foreign">ZP</span></td>
<td align="right"><span class="text-up">353,553 </span></td>
<td align="right"><span class="text-up">209.7 B</span></td>
<td align="right"><span class="text-up">5,931 </span></td>
<td align="center">1</td>
<td align="center"><span class="text-foreign">ZP</span></td>
<td align="right"><span class="text-down">362,463 </span></td>
<td align="right"><span class="text-down">215.0 B</span></td>
<td align="right"><span class="text-down">5,933 </span></td>
</tr>
<tr>
<td align="center"><span class="text-bumn">CC</span></td>
<td align="right"><span class="text-up">123,885 </span></td>
<td align="right"><span class="text-up">73.3 B</span></td>
<td align="right"><span class="text-up">5,919 </span></td>
<td align="center">2</td>
<td align="center"><span class="text-bumn">CC</span></td>
<td align="right"><span class="text-down">170,087 </span></td>
<td align="right"><span class="text-down">100.6 B</span></td>
<td align="right"><span class="text-down">5,914 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">BK</span></td>
<td align="right"><span class="text-up">119,389 </span></td>
<td align="right"><span class="text-up">70.6 B</span></td>
<td align="right"><span class="text-up">5,915 </span></td>
<td align="center">3</td>
<td align="center"><span class="text-foreign">KZ</span></td>
<td align="right"><span class="text-down">131,973 </span></td>
<td align="right"><span class="text-down">78.2 B</span></td>
<td align="right"><span class="text-down">5,922 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">TP</span></td>
<td align="right"><span class="text-up">114,823 </span></td>
<td align="right"><span class="text-up">68.1 B</span></td>
<td align="right"><span class="text-up">5,931 </span></td>
<td align="center">4</td>
<td align="center"><span class="text-foreign">AG</span></td>
<td align="right"><span class="text-down">113,249 </span></td>
<td align="right"><span class="text-down">67.1 B</span></td>
<td align="right"><span class="text-down">5,923 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">RX</span></td>
<td align="right"><span class="text-up">90,525 </span></td>
<td align="right"><span class="text-up">53.7 B</span></td>
<td align="right"><span class="text-up">5,931 </span></td>
<td align="center">5</td>
<td align="center"><span class="text-foreign">AK</span></td>
<td align="right"><span class="text-down">99,863 </span></td>
<td align="right"><span class="text-down">59.0 B</span></td>
<td align="right"><span class="text-down">5,904 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">AK</span></td>
<td align="right"><span class="text-up">84,572 </span></td>
<td align="right"><span class="text-up">50.1 B</span></td>
<td align="right"><span class="text-up">5,920 </span></td>
<td align="center">6</td>
<td align="center"><span class="text-foreign">BK</span></td>
<td align="right"><span class="text-down">87,628 </span></td>
<td align="right"><span class="text-down">51.8 B</span></td>
<td align="right"><span class="text-down">5,915 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">YU</span></td>
<td align="right"><span class="text-up">54,296 </span></td>
<td align="right"><span class="text-up">32.2 B</span></td>
<td align="right"><span class="text-up">5,928 </span></td>
<td align="center">7</td>
<td align="center"><span class="text-foreign">YU</span></td>
<td align="right"><span class="text-down">58,039 </span></td>
<td align="right"><span class="text-down">34.4 B</span></td>
<td align="right"><span class="text-down">5,926 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">XL</span></td>
<td align="right"><span class="text-up">44,062 </span></td>
<td align="right"><span class="text-up">26.0 B</span></td>
<td align="right"><span class="text-up">5,907 </span></td>
<td align="center">8</td>
<td align="center"><span class="text-local">XL</span></td>
<td align="right"><span class="text-down">31,516 </span></td>
<td align="right"><span class="text-down">18.6 B</span></td>
<td align="right"><span class="text-down">5,905 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">SQ</span></td>
<td align="right"><span class="text-up">38,109 </span></td>
<td align="right"><span class="text-up">22.5 B</span></td>
<td align="right"><span class="text-up">5,901 </span></td>
<td align="center">9</td>
<td align="center"><span class="text-local">SQ</span></td>
<td align="right"><span class="text-down">21,672 </span></td>
<td align="right"><span class="text-down">12.8 B</span></td>
<td align="right"><span class="text-down">5,917 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">YP</span></td>
<td align="right"><span class="text-up">22,957 </span></td>
<td align="right"><span class="text-up">13.5 B</span></td>
<td align="right"><span class="text-up">5,898 </span></td>
<td align="center">10</td>
<td align="center"><span class="text-local">BQ</span></td>
<td align="right"><span class="text-down">19,658 </span></td>
<td align="right"><span class="text-down">11.7 B</span></td>
<td align="right"><span class="text-down">5,937 </span></td>
</tr>
</tbody>
<tfoot>
<tr class="bdr-t">
<th colspan="9" align="center">
<div class="text-center pt20 valign-middle" style="height:50px;">
<span class="fsize12 ml5 mr5">T. Val : 738.2 B</span>
<span class="fsize12 ml5 mr5">F. NVal : 30.1 B</span>
<span class="fsize12 ml5 mr5">T.Lot : 1.2 M</span>
<span class="fsize12 ml5 mr5">Avg : 5,921 </span>
</div>
</th>
</tr>
</tfoot>
</table>
</div>
<!-- =======================
======SCRIPT MODULE=========
============================-->
<script src="../../../../module/saham/include/js/data-brokersummary.js"></script>

View file

@ -0,0 +1,181 @@
<div id="modContentBrokerSum"></div>
<!--DATA BROKER SUMMARY-->
<div class="pa5">
<div class="row mincol-3">
<div class="col col-xs-5 col-sm-3">
<input class="form-control input-xs text-center" id="startDateBrokerSummary" name="startDate" placeholder="DD Month YYYY" value="10/13/2026" type="text">
</div>
<div class="col col-xs-5 col-sm-3">
<input class="form-control input-xs text-center" id="endDateBrokerSummary" name="endDate" placeholder="DD Month YYYY" value="10/13/2026" type="text">
</div>
<div class="mt15 visible-xs">&nbsp;</div>
<div class="col col-xs-5 col-sm-3">
<select class="form-control input-xs" id="FDBrokerSummary">
<option value="all">All</option>
<option value="F" selected>Foreign</option>
<option value="D" >Domestic</option>
</select>
</div>
<div class="col col-xs-5 col-sm-2">
<select class="form-control input-xs" id="boardBrokerSummary">
<option value="all">All Trade</option>
<option value="RG" >Regular</option>
<option value="TN" >Tunai</option>
<option value="NG" >Nego</option>
</select>
</div>
<div class="col col-xs-2 col-sm-1">
<button type="button" class="btn btn-default btn-block btn-xs-input" onClick="quickinfo_brokersummary($('#stockPageCode').val()!==undefined?$('#stockPageCode').val():$('#stockInfoCode').val(),$('#startDateBrokerSummary').val(),$('#endDateBrokerSummary').val(),$('#FDBrokerSummary').val(),$('#boardBrokerSummary').val());">GO</button>
</div>
</div>
</div>
<div class="table-responsive">
<table width="100%" border="0" cellspacing="0" cellpadding="0" class="table table-summary table-hover noborder nm">
<thead>
<tr>
<th align="center">Buyer</th>
<th align="right">B.Lot</th>
<th align="right">B.Val</th>
<th align="right">B.Avg</th>
<th align="center">#</th>
<th align="center">Seller</th>
<th align="right">S.Lot</th>
<th align="right">S.Val</th>
<th align="right">S.Avg</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><span class="text-foreign">ZP</span></td>
<td align="right"><span class="text-up">347,552 </span></td>
<td align="right"><span class="text-up">206.1 B</span></td>
<td align="right"><span class="text-up">5,931 </span></td>
<td align="center">1</td>
<td align="center"><span class="text-foreign">ZP</span></td>
<td align="right"><span class="text-down">361,884 </span></td>
<td align="right"><span class="text-down">214.7 B</span></td>
<td align="right"><span class="text-down">5,933 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">BK</span></td>
<td align="right"><span class="text-up">119,389 </span></td>
<td align="right"><span class="text-up">70.6 B</span></td>
<td align="right"><span class="text-up">5,915 </span></td>
<td align="center">2</td>
<td align="center"><span class="text-bumn">CC</span></td>
<td align="right"><span class="text-down">134,566 </span></td>
<td align="right"><span class="text-down">79.6 B</span></td>
<td align="right"><span class="text-down">5,918 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">TP</span></td>
<td align="right"><span class="text-up">112,063 </span></td>
<td align="right"><span class="text-up">66.5 B</span></td>
<td align="right"><span class="text-up">5,932 </span></td>
<td align="center">3</td>
<td align="center"><span class="text-foreign">AG</span></td>
<td align="right"><span class="text-down">113,249 </span></td>
<td align="right"><span class="text-down">67.1 B</span></td>
<td align="right"><span class="text-down">5,923 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">RX</span></td>
<td align="right"><span class="text-up">90,525 </span></td>
<td align="right"><span class="text-up">53.7 B</span></td>
<td align="right"><span class="text-up">5,931 </span></td>
<td align="center">4</td>
<td align="center"><span class="text-foreign">AK</span></td>
<td align="right"><span class="text-down">99,863 </span></td>
<td align="right"><span class="text-down">59.0 B</span></td>
<td align="right"><span class="text-down">5,904 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">AK</span></td>
<td align="right"><span class="text-up">84,572 </span></td>
<td align="right"><span class="text-up">50.1 B</span></td>
<td align="right"><span class="text-up">5,920 </span></td>
<td align="center">5</td>
<td align="center"><span class="text-foreign">YU</span></td>
<td align="right"><span class="text-down">57,345 </span></td>
<td align="right"><span class="text-down">34.0 B</span></td>
<td align="right"><span class="text-down">5,927 </span></td>
</tr>
<tr>
<td align="center"><span class="text-bumn">CC</span></td>
<td align="right"><span class="text-up">82,117 </span></td>
<td align="right"><span class="text-up">48.7 B</span></td>
<td align="right"><span class="text-up">5,925 </span></td>
<td align="center">6</td>
<td align="center"><span class="text-foreign">BK</span></td>
<td align="right"><span class="text-down">49,256 </span></td>
<td align="right"><span class="text-down">29.1 B</span></td>
<td align="right"><span class="text-down">5,918 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">YU</span></td>
<td align="right"><span class="text-up">48,065 </span></td>
<td align="right"><span class="text-up">28.5 B</span></td>
<td align="right"><span class="text-up">5,932 </span></td>
<td align="center">7</td>
<td align="center"><span class="text-foreign">DP</span></td>
<td align="right"><span class="text-down">12,747 </span></td>
<td align="right"><span class="text-down">7.5 B</span></td>
<td align="right"><span class="text-down">5,917 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">HD</span></td>
<td align="right"><span class="text-up">12,342 </span></td>
<td align="right"><span class="text-up">7.3 B</span></td>
<td align="right"><span class="text-up">5,905 </span></td>
<td align="center">8</td>
<td align="center"><span class="text-foreign">KZ</span></td>
<td align="right"><span class="text-down">11,973 </span></td>
<td align="right"><span class="text-down">7.1 B</span></td>
<td align="right"><span class="text-down">5,924 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">KZ</span></td>
<td align="right"><span class="text-up">3,683 </span></td>
<td align="right"><span class="text-up">2.2 B</span></td>
<td align="right"><span class="text-up">5,929 </span></td>
<td align="center">9</td>
<td align="center"><span class="text-local">HD</span></td>
<td align="right"><span class="text-down">8,420 </span></td>
<td align="right"><span class="text-down">5.0 B</span></td>
<td align="right"><span class="text-down">5,925 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">EP</span></td>
<td align="right"><span class="text-up">3,000 </span></td>
<td align="right"><span class="text-up">1.8 B</span></td>
<td align="right"><span class="text-up">5,925 </span></td>
<td align="center">10</td>
<td align="center"><span class="text-local">BQ</span></td>
<td align="right"><span class="text-down">5,000 </span></td>
<td align="right"><span class="text-down">3.0 B</span></td>
<td align="right"><span class="text-down">5,925 </span></td>
</tr>
</tbody>
<tfoot>
<tr class="bdr-t">
<th colspan="9" align="center">
<div class="text-center pt20 valign-middle" style="height:50px;">
<span class="fsize12 ml5 mr5">T. Val : 537.3 B</span>
<span class="fsize12 ml5 mr5">F. NVal : 30.1 B</span>
<span class="fsize12 ml5 mr5">T.Lot : 906,459 </span>
<span class="fsize12 ml5 mr5">Avg : 5,927 </span>
</div>
</th>
</tr>
</tfoot>
</table>
</div>
<!-- =======================
======SCRIPT MODULE=========
============================-->
<script src="../../../../module/saham/include/js/data-brokersummary.js"></script>

View file

@ -0,0 +1,181 @@
<div id="modContentBrokerSum"></div>
<!--DATA BROKER SUMMARY-->
<div class="pa5">
<div class="row mincol-3">
<div class="col col-xs-5 col-sm-3">
<input class="form-control input-xs text-center" id="startDateBrokerSummary" name="startDate" placeholder="DD Month YYYY" value="10/14/2026" type="text">
</div>
<div class="col col-xs-5 col-sm-3">
<input class="form-control input-xs text-center" id="endDateBrokerSummary" name="endDate" placeholder="DD Month YYYY" value="10/14/2026" type="text">
</div>
<div class="mt15 visible-xs">&nbsp;</div>
<div class="col col-xs-5 col-sm-3">
<select class="form-control input-xs" id="FDBrokerSummary">
<option value="all">All</option>
<option value="F" >Foreign</option>
<option value="D" >Domestic</option>
</select>
</div>
<div class="col col-xs-5 col-sm-2">
<select class="form-control input-xs" id="boardBrokerSummary">
<option value="all">All Trade</option>
<option value="RG" >Regular</option>
<option value="TN" >Tunai</option>
<option value="NG" >Nego</option>
</select>
</div>
<div class="col col-xs-2 col-sm-1">
<button type="button" class="btn btn-default btn-block btn-xs-input" onClick="quickinfo_brokersummary($('#stockPageCode').val()!==undefined?$('#stockPageCode').val():$('#stockInfoCode').val(),$('#startDateBrokerSummary').val(),$('#endDateBrokerSummary').val(),$('#FDBrokerSummary').val(),$('#boardBrokerSummary').val());">GO</button>
</div>
</div>
</div>
<div class="table-responsive">
<table width="100%" border="0" cellspacing="0" cellpadding="0" class="table table-summary table-hover noborder nm">
<thead>
<tr>
<th align="center">Buyer</th>
<th align="right">B.Lot</th>
<th align="right">B.Val</th>
<th align="right">B.Avg</th>
<th align="center">#</th>
<th align="center">Seller</th>
<th align="right">S.Lot</th>
<th align="right">S.Val</th>
<th align="right">S.Avg</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><span class="text-foreign">KZ</span></td>
<td align="right"><span class="text-up">554,786 </span></td>
<td align="right"><span class="text-up">338.9 B</span></td>
<td align="right"><span class="text-up">6,109 </span></td>
<td align="center">1</td>
<td align="center"><span class="text-foreign">AK</span></td>
<td align="right"><span class="text-down">525,906 </span></td>
<td align="right"><span class="text-down">319.8 B</span></td>
<td align="right"><span class="text-down">6,081 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">AK</span></td>
<td align="right"><span class="text-up">485,138 </span></td>
<td align="right"><span class="text-up">294.7 B</span></td>
<td align="right"><span class="text-up">6,075 </span></td>
<td align="center">2</td>
<td align="center"><span class="text-foreign">ZP</span></td>
<td align="right"><span class="text-down">377,446 </span></td>
<td align="right"><span class="text-down">230.0 B</span></td>
<td align="right"><span class="text-down">6,094 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">YU</span></td>
<td align="right"><span class="text-up">187,056 </span></td>
<td align="right"><span class="text-up">113.8 B</span></td>
<td align="right"><span class="text-up">6,085 </span></td>
<td align="center">3</td>
<td align="center"><span class="text-foreign">KZ</span></td>
<td align="right"><span class="text-down">164,627 </span></td>
<td align="right"><span class="text-down">100.4 B</span></td>
<td align="right"><span class="text-down">6,096 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">ZP</span></td>
<td align="right"><span class="text-up">160,092 </span></td>
<td align="right"><span class="text-up">97.1 B</span></td>
<td align="right"><span class="text-up">6,062 </span></td>
<td align="center">4</td>
<td align="center"><span class="text-local">SQ</span></td>
<td align="right"><span class="text-down">103,273 </span></td>
<td align="right"><span class="text-down">62.8 B</span></td>
<td align="right"><span class="text-down">6,082 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">RX</span></td>
<td align="right"><span class="text-up">79,012 </span></td>
<td align="right"><span class="text-up">48.0 B</span></td>
<td align="right"><span class="text-up">6,071 </span></td>
<td align="center">5</td>
<td align="center"><span class="text-foreign">BK</span></td>
<td align="right"><span class="text-down">98,516 </span></td>
<td align="right"><span class="text-down">59.8 B</span></td>
<td align="right"><span class="text-down">6,070 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">BK</span></td>
<td align="right"><span class="text-up">70,651 </span></td>
<td align="right"><span class="text-up">43.1 B</span></td>
<td align="right"><span class="text-up">6,096 </span></td>
<td align="center">6</td>
<td align="center"><span class="text-foreign">YU</span></td>
<td align="right"><span class="text-down">73,085 </span></td>
<td align="right"><span class="text-down">44.6 B</span></td>
<td align="right"><span class="text-down">6,098 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">BB</span></td>
<td align="right"><span class="text-up">65,000 </span></td>
<td align="right"><span class="text-up">39.5 B</span></td>
<td align="right"><span class="text-up">6,073 </span></td>
<td align="center">7</td>
<td align="center"><span class="text-foreign">YP</span></td>
<td align="right"><span class="text-down">51,697 </span></td>
<td align="right"><span class="text-down">31.4 B</span></td>
<td align="right"><span class="text-down">6,075 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">BQ</span></td>
<td align="right"><span class="text-up">31,865 </span></td>
<td align="right"><span class="text-up">19.2 B</span></td>
<td align="right"><span class="text-up">6,027 </span></td>
<td align="center">8</td>
<td align="center"><span class="text-bumn">CC</span></td>
<td align="right"><span class="text-down">48,169 </span></td>
<td align="right"><span class="text-down">29.3 B</span></td>
<td align="right"><span class="text-down">6,082 </span></td>
</tr>
<tr>
<td align="center"><span class="text-bumn">CC</span></td>
<td align="right"><span class="text-up">30,758 </span></td>
<td align="right"><span class="text-up">18.7 B</span></td>
<td align="right"><span class="text-up">6,066 </span></td>
<td align="center">9</td>
<td align="center"><span class="text-local">XL</span></td>
<td align="right"><span class="text-down">46,536 </span></td>
<td align="right"><span class="text-down">28.3 B</span></td>
<td align="right"><span class="text-down">6,074 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">YP</span></td>
<td align="right"><span class="text-up">27,270 </span></td>
<td align="right"><span class="text-up">16.6 B</span></td>
<td align="right"><span class="text-up">6,085 </span></td>
<td align="center">10</td>
<td align="center"><span class="text-local">BQ</span></td>
<td align="right"><span class="text-down">39,660 </span></td>
<td align="right"><span class="text-down">24.1 B</span></td>
<td align="right"><span class="text-down">6,070 </span></td>
</tr>
</tbody>
<tfoot>
<tr class="bdr-t">
<th colspan="9" align="center">
<div class="text-center pt20 valign-middle" style="height:50px;">
<span class="fsize12 ml5 mr5">T. Val : 1.1 T</span>
<span class="fsize12 ml5 mr5">F. NVal : 216.6 B</span>
<span class="fsize12 ml5 mr5">T.Lot : 1.8 M</span>
<span class="fsize12 ml5 mr5">Avg : 6,084 </span>
</div>
</th>
</tr>
</tfoot>
</table>
</div>
<!-- =======================
======SCRIPT MODULE=========
============================-->
<script src="../../../../module/saham/include/js/data-brokersummary.js"></script>

View file

@ -0,0 +1,181 @@
<div id="modContentBrokerSum"></div>
<!--DATA BROKER SUMMARY-->
<div class="pa5">
<div class="row mincol-3">
<div class="col col-xs-5 col-sm-3">
<input class="form-control input-xs text-center" id="startDateBrokerSummary" name="startDate" placeholder="DD Month YYYY" value="10/14/2026" type="text">
</div>
<div class="col col-xs-5 col-sm-3">
<input class="form-control input-xs text-center" id="endDateBrokerSummary" name="endDate" placeholder="DD Month YYYY" value="10/14/2026" type="text">
</div>
<div class="mt15 visible-xs">&nbsp;</div>
<div class="col col-xs-5 col-sm-3">
<select class="form-control input-xs" id="FDBrokerSummary">
<option value="all">All</option>
<option value="F" selected>Foreign</option>
<option value="D" >Domestic</option>
</select>
</div>
<div class="col col-xs-5 col-sm-2">
<select class="form-control input-xs" id="boardBrokerSummary">
<option value="all">All Trade</option>
<option value="RG" >Regular</option>
<option value="TN" >Tunai</option>
<option value="NG" >Nego</option>
</select>
</div>
<div class="col col-xs-2 col-sm-1">
<button type="button" class="btn btn-default btn-block btn-xs-input" onClick="quickinfo_brokersummary($('#stockPageCode').val()!==undefined?$('#stockPageCode').val():$('#stockInfoCode').val(),$('#startDateBrokerSummary').val(),$('#endDateBrokerSummary').val(),$('#FDBrokerSummary').val(),$('#boardBrokerSummary').val());">GO</button>
</div>
</div>
</div>
<div class="table-responsive">
<table width="100%" border="0" cellspacing="0" cellpadding="0" class="table table-summary table-hover noborder nm">
<thead>
<tr>
<th align="center">Buyer</th>
<th align="right">B.Lot</th>
<th align="right">B.Val</th>
<th align="right">B.Avg</th>
<th align="center">#</th>
<th align="center">Seller</th>
<th align="right">S.Lot</th>
<th align="right">S.Val</th>
<th align="right">S.Avg</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><span class="text-foreign">KZ</span></td>
<td align="right"><span class="text-up">554,012 </span></td>
<td align="right"><span class="text-up">338.5 B</span></td>
<td align="right"><span class="text-up">6,109 </span></td>
<td align="center">1</td>
<td align="center"><span class="text-foreign">AK</span></td>
<td align="right"><span class="text-down">525,906 </span></td>
<td align="right"><span class="text-down">319.8 B</span></td>
<td align="right"><span class="text-down">6,081 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">AK</span></td>
<td align="right"><span class="text-up">485,138 </span></td>
<td align="right"><span class="text-up">294.7 B</span></td>
<td align="right"><span class="text-up">6,075 </span></td>
<td align="center">2</td>
<td align="center"><span class="text-foreign">ZP</span></td>
<td align="right"><span class="text-down">368,595 </span></td>
<td align="right"><span class="text-down">224.6 B</span></td>
<td align="right"><span class="text-down">6,094 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">YU</span></td>
<td align="right"><span class="text-up">187,042 </span></td>
<td align="right"><span class="text-up">113.8 B</span></td>
<td align="right"><span class="text-up">6,085 </span></td>
<td align="center">3</td>
<td align="center"><span class="text-foreign">KZ</span></td>
<td align="right"><span class="text-down">163,864 </span></td>
<td align="right"><span class="text-down">99.9 B</span></td>
<td align="right"><span class="text-down">6,096 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">ZP</span></td>
<td align="right"><span class="text-up">156,660 </span></td>
<td align="right"><span class="text-up">95.0 B</span></td>
<td align="right"><span class="text-up">6,063 </span></td>
<td align="center">4</td>
<td align="center"><span class="text-foreign">BK</span></td>
<td align="right"><span class="text-down">98,516 </span></td>
<td align="right"><span class="text-down">59.8 B</span></td>
<td align="right"><span class="text-down">6,070 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">RX</span></td>
<td align="right"><span class="text-up">79,012 </span></td>
<td align="right"><span class="text-up">48.0 B</span></td>
<td align="right"><span class="text-up">6,071 </span></td>
<td align="center">5</td>
<td align="center"><span class="text-foreign">YU</span></td>
<td align="right"><span class="text-down">59,747 </span></td>
<td align="right"><span class="text-down">36.5 B</span></td>
<td align="right"><span class="text-down">6,103 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">BK</span></td>
<td align="right"><span class="text-up">70,651 </span></td>
<td align="right"><span class="text-up">43.1 B</span></td>
<td align="right"><span class="text-up">6,096 </span></td>
<td align="center">6</td>
<td align="center"><span class="text-local">BB</span></td>
<td align="right"><span class="text-down">20,374 </span></td>
<td align="right"><span class="text-down">12.4 B</span></td>
<td align="right"><span class="text-down">6,074 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">BB</span></td>
<td align="right"><span class="text-up">65,000 </span></td>
<td align="right"><span class="text-up">39.5 B</span></td>
<td align="right"><span class="text-up">6,073 </span></td>
<td align="center">7</td>
<td align="center"><span class="text-local">HD</span></td>
<td align="right"><span class="text-down">17,554 </span></td>
<td align="right"><span class="text-down">10.6 B</span></td>
<td align="right"><span class="text-down">6,056 </span></td>
</tr>
<tr>
<td align="center"><span class="text-bumn">CC</span></td>
<td align="right"><span class="text-up">17,593 </span></td>
<td align="right"><span class="text-up">10.7 B</span></td>
<td align="right"><span class="text-up">6,067 </span></td>
<td align="center">8</td>
<td align="center"><span class="text-bumn">CC</span></td>
<td align="right"><span class="text-down">12,729 </span></td>
<td align="right"><span class="text-down">7.8 B</span></td>
<td align="right"><span class="text-down">6,094 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">HD</span></td>
<td align="right"><span class="text-up">13,632 </span></td>
<td align="right"><span class="text-up">8.2 B</span></td>
<td align="right"><span class="text-up">6,050 </span></td>
<td align="center">9</td>
<td align="center"><span class="text-local">EP</span></td>
<td align="right"><span class="text-down">3,000 </span></td>
<td align="right"><span class="text-down">1.8 B</span></td>
<td align="right"><span class="text-down">6,075 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">DP</span></td>
<td align="right"><span class="text-up">474 </span></td>
<td align="right"><span class="text-up">288.2 M</span></td>
<td align="right"><span class="text-up">6,080 </span></td>
<td align="center">10</td>
<td align="center"><span class="text-local">IU</span></td>
<td align="right"><span class="text-down">1,200 </span></td>
<td align="right"><span class="text-down">729.0 M</span></td>
<td align="right"><span class="text-down">6,075 </span></td>
</tr>
</tbody>
<tfoot>
<tr class="bdr-t">
<th colspan="9" align="center">
<div class="text-center pt20 valign-middle" style="height:50px;">
<span class="fsize12 ml5 mr5">T. Val : 991.8 B</span>
<span class="fsize12 ml5 mr5">F. NVal : 216.6 B</span>
<span class="fsize12 ml5 mr5">T.Lot : 1.6 M</span>
<span class="fsize12 ml5 mr5">Avg : 6,087 </span>
</div>
</th>
</tr>
</tfoot>
</table>
</div>
<!-- =======================
======SCRIPT MODULE=========
============================-->
<script src="../../../../module/saham/include/js/data-brokersummary.js"></script>

View file

@ -0,0 +1,181 @@
<div id="modContentBrokerSum"></div>
<!--DATA BROKER SUMMARY-->
<div class="pa5">
<div class="row mincol-3">
<div class="col col-xs-5 col-sm-3">
<input class="form-control input-xs text-center" id="startDateBrokerSummary" name="startDate" placeholder="DD Month YYYY" value="10/15/2026" type="text">
</div>
<div class="col col-xs-5 col-sm-3">
<input class="form-control input-xs text-center" id="endDateBrokerSummary" name="endDate" placeholder="DD Month YYYY" value="10/15/2026" type="text">
</div>
<div class="mt15 visible-xs">&nbsp;</div>
<div class="col col-xs-5 col-sm-3">
<select class="form-control input-xs" id="FDBrokerSummary">
<option value="all">All</option>
<option value="F" >Foreign</option>
<option value="D" >Domestic</option>
</select>
</div>
<div class="col col-xs-5 col-sm-2">
<select class="form-control input-xs" id="boardBrokerSummary">
<option value="all">All Trade</option>
<option value="RG" >Regular</option>
<option value="TN" >Tunai</option>
<option value="NG" >Nego</option>
</select>
</div>
<div class="col col-xs-2 col-sm-1">
<button type="button" class="btn btn-default btn-block btn-xs-input" onClick="quickinfo_brokersummary($('#stockPageCode').val()!==undefined?$('#stockPageCode').val():$('#stockInfoCode').val(),$('#startDateBrokerSummary').val(),$('#endDateBrokerSummary').val(),$('#FDBrokerSummary').val(),$('#boardBrokerSummary').val());">GO</button>
</div>
</div>
</div>
<div class="table-responsive">
<table width="100%" border="0" cellspacing="0" cellpadding="0" class="table table-summary table-hover noborder nm">
<thead>
<tr>
<th align="center">Buyer</th>
<th align="right">B.Lot</th>
<th align="right">B.Val</th>
<th align="right">B.Avg</th>
<th align="center">#</th>
<th align="center">Seller</th>
<th align="right">S.Lot</th>
<th align="right">S.Val</th>
<th align="right">S.Avg</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><span class="text-foreign">YU</span></td>
<td align="right"><span class="text-up">447,791 </span></td>
<td align="right"><span class="text-up">271.5 B</span></td>
<td align="right"><span class="text-up">6,064 </span></td>
<td align="center">1</td>
<td align="center"><span class="text-bumn">CC</span></td>
<td align="right"><span class="text-down">335,078 </span></td>
<td align="right"><span class="text-down">203.1 B</span></td>
<td align="right"><span class="text-down">6,062 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">ZP</span></td>
<td align="right"><span class="text-up">284,818 </span></td>
<td align="right"><span class="text-up">172.7 B</span></td>
<td align="right"><span class="text-up">6,062 </span></td>
<td align="center">2</td>
<td align="center"><span class="text-foreign">YU</span></td>
<td align="right"><span class="text-down">229,539 </span></td>
<td align="right"><span class="text-down">139.1 B</span></td>
<td align="right"><span class="text-down">6,061 </span></td>
</tr>
<tr>
<td align="center"><span class="text-bumn">DX</span></td>
<td align="right"><span class="text-up">194,491 </span></td>
<td align="right"><span class="text-up">117.8 B</span></td>
<td align="right"><span class="text-up">6,059 </span></td>
<td align="center">3</td>
<td align="center"><span class="text-foreign">ZP</span></td>
<td align="right"><span class="text-down">225,482 </span></td>
<td align="right"><span class="text-down">136.6 B</span></td>
<td align="right"><span class="text-down">6,059 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">KZ</span></td>
<td align="right"><span class="text-up">138,619 </span></td>
<td align="right"><span class="text-up">84.0 B</span></td>
<td align="right"><span class="text-up">6,058 </span></td>
<td align="center">4</td>
<td align="center"><span class="text-foreign">AK</span></td>
<td align="right"><span class="text-down">179,064 </span></td>
<td align="right"><span class="text-down">108.5 B</span></td>
<td align="right"><span class="text-down">6,059 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">AK</span></td>
<td align="right"><span class="text-up">123,061 </span></td>
<td align="right"><span class="text-up">74.5 B</span></td>
<td align="right"><span class="text-up">6,057 </span></td>
<td align="center">5</td>
<td align="center"><span class="text-local">BB</span></td>
<td align="right"><span class="text-down">143,526 </span></td>
<td align="right"><span class="text-down">86.9 B</span></td>
<td align="right"><span class="text-down">6,053 </span></td>
</tr>
<tr>
<td align="center"><span class="text-bumn">CC</span></td>
<td align="right"><span class="text-up">78,820 </span></td>
<td align="right"><span class="text-up">47.7 B</span></td>
<td align="right"><span class="text-up">6,050 </span></td>
<td align="center">6</td>
<td align="center"><span class="text-foreign">BK</span></td>
<td align="right"><span class="text-down">68,285 </span></td>
<td align="right"><span class="text-down">41.3 B</span></td>
<td align="right"><span class="text-down">6,049 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">RX</span></td>
<td align="right"><span class="text-up">72,951 </span></td>
<td align="right"><span class="text-up">44.2 B</span></td>
<td align="right"><span class="text-up">6,055 </span></td>
<td align="center">7</td>
<td align="center"><span class="text-local">SQ</span></td>
<td align="right"><span class="text-down">63,839 </span></td>
<td align="right"><span class="text-down">38.6 B</span></td>
<td align="right"><span class="text-down">6,050 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">BK</span></td>
<td align="right"><span class="text-up">36,383 </span></td>
<td align="right"><span class="text-up">22.1 B</span></td>
<td align="right"><span class="text-up">6,066 </span></td>
<td align="center">8</td>
<td align="center"><span class="text-local">CP</span></td>
<td align="right"><span class="text-down">49,275 </span></td>
<td align="right"><span class="text-down">29.9 B</span></td>
<td align="right"><span class="text-down">6,059 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">SQ</span></td>
<td align="right"><span class="text-up">26,718 </span></td>
<td align="right"><span class="text-up">16.1 B</span></td>
<td align="right"><span class="text-up">6,039 </span></td>
<td align="center">9</td>
<td align="center"><span class="text-local">XL</span></td>
<td align="right"><span class="text-down">34,325 </span></td>
<td align="right"><span class="text-down">20.8 B</span></td>
<td align="right"><span class="text-down">6,068 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">XL</span></td>
<td align="right"><span class="text-up">24,529 </span></td>
<td align="right"><span class="text-up">14.8 B</span></td>
<td align="right"><span class="text-up">6,052 </span></td>
<td align="center">10</td>
<td align="center"><span class="text-local">PD</span></td>
<td align="right"><span class="text-down">27,152 </span></td>
<td align="right"><span class="text-down">16.4 B</span></td>
<td align="right"><span class="text-down">6,053 </span></td>
</tr>
</tbody>
<tfoot>
<tr class="bdr-t">
<th colspan="9" align="center">
<div class="text-center pt20 valign-middle" style="height:50px;">
<span class="fsize12 ml5 mr5">T. Val : 912.0 B</span>
<span class="fsize12 ml5 mr5">F. NVal : 286.1 B</span>
<span class="fsize12 ml5 mr5">T.Lot : 1.5 M</span>
<span class="fsize12 ml5 mr5">Avg : 6,060 </span>
</div>
</th>
</tr>
</tfoot>
</table>
</div>
<!-- =======================
======SCRIPT MODULE=========
============================-->
<script src="../../../../module/saham/include/js/data-brokersummary.js"></script>

View file

@ -0,0 +1,181 @@
<div id="modContentBrokerSum"></div>
<!--DATA BROKER SUMMARY-->
<div class="pa5">
<div class="row mincol-3">
<div class="col col-xs-5 col-sm-3">
<input class="form-control input-xs text-center" id="startDateBrokerSummary" name="startDate" placeholder="DD Month YYYY" value="10/15/2026" type="text">
</div>
<div class="col col-xs-5 col-sm-3">
<input class="form-control input-xs text-center" id="endDateBrokerSummary" name="endDate" placeholder="DD Month YYYY" value="10/15/2026" type="text">
</div>
<div class="mt15 visible-xs">&nbsp;</div>
<div class="col col-xs-5 col-sm-3">
<select class="form-control input-xs" id="FDBrokerSummary">
<option value="all">All</option>
<option value="F" selected>Foreign</option>
<option value="D" >Domestic</option>
</select>
</div>
<div class="col col-xs-5 col-sm-2">
<select class="form-control input-xs" id="boardBrokerSummary">
<option value="all">All Trade</option>
<option value="RG" >Regular</option>
<option value="TN" >Tunai</option>
<option value="NG" >Nego</option>
</select>
</div>
<div class="col col-xs-2 col-sm-1">
<button type="button" class="btn btn-default btn-block btn-xs-input" onClick="quickinfo_brokersummary($('#stockPageCode').val()!==undefined?$('#stockPageCode').val():$('#stockInfoCode').val(),$('#startDateBrokerSummary').val(),$('#endDateBrokerSummary').val(),$('#FDBrokerSummary').val(),$('#boardBrokerSummary').val());">GO</button>
</div>
</div>
</div>
<div class="table-responsive">
<table width="100%" border="0" cellspacing="0" cellpadding="0" class="table table-summary table-hover noborder nm">
<thead>
<tr>
<th align="center">Buyer</th>
<th align="right">B.Lot</th>
<th align="right">B.Val</th>
<th align="right">B.Avg</th>
<th align="center">#</th>
<th align="center">Seller</th>
<th align="right">S.Lot</th>
<th align="right">S.Val</th>
<th align="right">S.Avg</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><span class="text-foreign">YU</span></td>
<td align="right"><span class="text-up">445,640 </span></td>
<td align="right"><span class="text-up">270.2 B</span></td>
<td align="right"><span class="text-up">6,064 </span></td>
<td align="center">1</td>
<td align="center"><span class="text-foreign">YU</span></td>
<td align="right"><span class="text-down">224,007 </span></td>
<td align="right"><span class="text-down">135.8 B</span></td>
<td align="right"><span class="text-down">6,061 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">ZP</span></td>
<td align="right"><span class="text-up">283,676 </span></td>
<td align="right"><span class="text-up">172.0 B</span></td>
<td align="right"><span class="text-up">6,062 </span></td>
<td align="center">2</td>
<td align="center"><span class="text-foreign">ZP</span></td>
<td align="right"><span class="text-down">197,862 </span></td>
<td align="right"><span class="text-down">119.9 B</span></td>
<td align="right"><span class="text-down">6,058 </span></td>
</tr>
<tr>
<td align="center"><span class="text-bumn">DX</span></td>
<td align="right"><span class="text-up">192,771 </span></td>
<td align="right"><span class="text-up">116.8 B</span></td>
<td align="right"><span class="text-up">6,059 </span></td>
<td align="center">3</td>
<td align="center"><span class="text-bumn">CC</span></td>
<td align="right"><span class="text-down">185,436 </span></td>
<td align="right"><span class="text-down">112.5 B</span></td>
<td align="right"><span class="text-down">6,066 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">KZ</span></td>
<td align="right"><span class="text-up">138,619 </span></td>
<td align="right"><span class="text-up">84.0 B</span></td>
<td align="right"><span class="text-up">6,058 </span></td>
<td align="center">4</td>
<td align="center"><span class="text-local">BB</span></td>
<td align="right"><span class="text-down">118,526 </span></td>
<td align="right"><span class="text-down">71.7 B</span></td>
<td align="right"><span class="text-down">6,053 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">AK</span></td>
<td align="right"><span class="text-up">122,365 </span></td>
<td align="right"><span class="text-up">74.1 B</span></td>
<td align="right"><span class="text-up">6,057 </span></td>
<td align="center">5</td>
<td align="center"><span class="text-foreign">AK</span></td>
<td align="right"><span class="text-down">109,064 </span></td>
<td align="right"><span class="text-down">66.1 B</span></td>
<td align="right"><span class="text-down">6,064 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">RX</span></td>
<td align="right"><span class="text-up">72,951 </span></td>
<td align="right"><span class="text-up">44.2 B</span></td>
<td align="right"><span class="text-up">6,055 </span></td>
<td align="center">6</td>
<td align="center"><span class="text-foreign">KZ</span></td>
<td align="right"><span class="text-down">25,617 </span></td>
<td align="right"><span class="text-down">15.5 B</span></td>
<td align="right"><span class="text-down">6,068 </span></td>
</tr>
<tr>
<td align="center"><span class="text-bumn">CC</span></td>
<td align="right"><span class="text-up">65,609 </span></td>
<td align="right"><span class="text-up">39.7 B</span></td>
<td align="right"><span class="text-up">6,051 </span></td>
<td align="center">7</td>
<td align="center"><span class="text-foreign">BK</span></td>
<td align="right"><span class="text-down">15,460 </span></td>
<td align="right"><span class="text-down">9.3 B</span></td>
<td align="right"><span class="text-down">6,044 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">BK</span></td>
<td align="right"><span class="text-up">36,383 </span></td>
<td align="right"><span class="text-up">22.1 B</span></td>
<td align="right"><span class="text-up">6,066 </span></td>
<td align="center">8</td>
<td align="center"><span class="text-foreign">RX</span></td>
<td align="right"><span class="text-down">11,033 </span></td>
<td align="right"><span class="text-down">6.7 B</span></td>
<td align="right"><span class="text-down">6,069 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">HD</span></td>
<td align="right"><span class="text-up">6,018 </span></td>
<td align="right"><span class="text-up">3.6 B</span></td>
<td align="right"><span class="text-up">6,053 </span></td>
<td align="center">9</td>
<td align="center"><span class="text-local">HD</span></td>
<td align="right"><span class="text-down">4,283 </span></td>
<td align="right"><span class="text-down">2.6 B</span></td>
<td align="right"><span class="text-down">6,058 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">AI</span></td>
<td align="right"><span class="text-up">200 </span></td>
<td align="right"><span class="text-up">120.5 M</span></td>
<td align="right"><span class="text-up">6,025 </span></td>
<td align="center">10</td>
<td align="center"><span class="text-foreign">YP</span></td>
<td align="right"><span class="text-down">550 </span></td>
<td align="right"><span class="text-down">332.9 M</span></td>
<td align="right"><span class="text-down">6,052 </span></td>
</tr>
</tbody>
<tfoot>
<tr class="bdr-t">
<th colspan="9" align="center">
<div class="text-center pt20 valign-middle" style="height:50px;">
<span class="fsize12 ml5 mr5">T. Val : 826.9 B</span>
<span class="fsize12 ml5 mr5">F. NVal : 286.1 B</span>
<span class="fsize12 ml5 mr5">T.Lot : 1.4 M</span>
<span class="fsize12 ml5 mr5">Avg : 6,061 </span>
</div>
</th>
</tr>
</tfoot>
</table>
</div>
<!-- =======================
======SCRIPT MODULE=========
============================-->
<script src="../../../../module/saham/include/js/data-brokersummary.js"></script>

View file

@ -3,6 +3,7 @@
use idx_core::api::types::{Interval, Period};
use idx_core::broker::types::{BrokerBoard, BrokerInvestor};
use idx_core::flow::types::FlowSource;
use idx_core::market::{ScreenFilter, ScreenRegion};
use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
use serde::Deserialize;
@ -170,6 +171,12 @@ pub struct FlowParams {
/// Last session of the range, YYYY-MM-DD (default today).
#[serde(default)]
pub to: Option<String>,
/// Where the figures come from (default "ipot"): "ipot" gives foreign buy, sell and net
/// value across all boards; "idx" gives exact share counts, the close and the non-regular
/// share, but needs curl-impersonate and may be blocked (HTTP 403).
#[serde(default)]
#[schemars(schema_with = "flow_source_schema")]
pub source: Option<String>,
}
#[derive(Debug, Deserialize, JsonSchema)]
@ -244,3 +251,7 @@ fn investor_schema(_: &mut SchemaGenerator) -> Schema {
fn board_schema(_: &mut SchemaGenerator) -> Schema {
string_enum(BrokerBoard::ALL.map(BrokerBoard::as_str).to_vec())
}
fn flow_source_schema(_: &mut SchemaGenerator) -> Schema {
string_enum(FlowSource::ALL.map(FlowSource::as_str).to_vec())
}

View file

@ -246,11 +246,12 @@ foreign or domestic, shares are of that side's own buying (`buy_value`) and sell
#[tool(
description = "Foreign investors' buying and selling of one IDX ticker, session by \
session (oldest first), from IDX's daily trading summary: foreign buy, sell and net volume in \
shares, `foreign_net_value` (net shares at the session's average price, an estimate, IDR), \
`change_bps`, and `non_regular_bps` (share of all value traded outside the regular board). \
Volume, value and foreign figures are the regular board. Totals cover the whole range; \
`foreign_share_bps` is foreign investors' share of regular-board trading. End of day."
session (oldest first), with range totals; end of day. With `source` \"ipot\" (default): \
`foreign_buy_value`, `foreign_sell_value` and `foreign_net_value` in IDR across all boards, \
`value` traded, and `foreign_share_bps` (foreign share of trading); share counts, close and \
`non_regular_bps` are null, and `rounded` = true means one decimal of precision. With \
\"idx\": exact foreign share counts on the regular board, values estimated at the session's \
average price, the close, `change_bps` and `non_regular_bps`."
)]
async fn get_foreign_flow(&self, params: Parameters<FlowParams>) -> ToolResult {
let FlowParams {
@ -258,11 +259,13 @@ Volume, value and foreign figures are the regular board. Totals cover the whole
days,
from,
to,
source,
} = params.0;
self.live(move |idx| {
let flow = idx.flow();
let range = FlowRange::from_args(days, from.as_deref(), to.as_deref(), flow.today())?;
flow.foreign(&symbol, range, CachePolicy::Default)
let source = source.as_deref().unwrap_or("ipot").parse()?;
flow.foreign(&symbol, range, source, CachePolicy::Default)
})
.await
}

View file

@ -236,7 +236,7 @@
"name": "get_company"
},
{
"description": "Foreign investors' buying and selling of one IDX ticker, session by session (oldest first), from IDX's daily trading summary: foreign buy, sell and net volume in shares, `foreign_net_value` (net shares at the session's average price, an estimate, IDR), `change_bps`, and `non_regular_bps` (share of all value traded outside the regular board). Volume, value and foreign figures are the regular board. Totals cover the whole range; `foreign_share_bps` is foreign investors' share of regular-board trading. End of day.",
"description": "Foreign investors' buying and selling of one IDX ticker, session by session (oldest first), with range totals; end of day. With `source` \"ipot\" (default): `foreign_buy_value`, `foreign_sell_value` and `foreign_net_value` in IDR across all boards, `value` traded, and `foreign_share_bps` (foreign share of trading); share counts, close and `non_regular_bps` are null, and `rounded` = true means one decimal of precision. With \"idx\": exact foreign share counts on the regular board, values estimated at the session's average price, the close, `change_bps` and `non_regular_bps`.",
"inputSchema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
@ -258,6 +258,15 @@
"null"
]
},
"source": {
"default": null,
"description": "Where the figures come from (default \"ipot\"): \"ipot\" gives foreign buy, sell and net\nvalue across all boards; \"idx\" gives exact share counts, the close and the non-regular\nshare, but needs curl-impersonate and may be blocked (HTTP 403).",
"enum": [
"ipot",
"idx"
],
"type": "string"
},
"symbol": {
"description": "IDX ticker, e.g. \"BBCA\".",
"type": "string"

View file

@ -281,8 +281,18 @@ fn live_tools_wrap_data_with_freshness() {
json!({"symbol": "BBCA", "from": "2026-10-12", "to": "2026-10-16"}),
);
assert_fetched(&flow);
assert_eq!(flow["source"], "idx");
assert_eq!(flow["source"], "ipot");
assert_eq!(flow["data"]["days"].as_array().unwrap().len(), 5);
assert_eq!(
flow["data"]["days"][4]["foreign_sell_value"],
4_000_000_000_000_i64
);
let idx_flow = client.ok(
"get_foreign_flow",
json!({"symbol": "BBCA", "from": "2026-10-12", "to": "2026-10-16", "source": "idx"}),
);
assert_eq!(idx_flow["source"], "idx");
assert_eq!(idx_flow["data"]["days"][4]["close"], 5175);
let compared = client.ok("compare_stocks", json!({"symbols": ["BBCA", "BBRI"]}));
assert_eq!(compared["reports"].as_array().unwrap().len(), 2);
@ -321,6 +331,10 @@ fn errors_carry_cli_codes() {
("analyze_bandar", json!({"symbols": ["BBCA"], "top": 4})),
("analyze_bandar", json!({"symbols": []})),
("get_foreign_flow", json!({"symbol": "BBCA", "days": 61})),
(
"get_foreign_flow",
json!({"symbol": "BBCA", "source": "rti"}),
),
(
"get_foreign_flow",
json!({"symbol": "BBCA", "days": 3, "from": "2026-10-12"}),

View file

@ -53,8 +53,8 @@ command parses arguments, makes one idx-core call, and renders the result.
- **Yahoo Finance** — default/auto history source. Reliable OHLCV data via `/v8/finance/chart/`.
- **IPOT (Indo Premier)** — broker summaries, whichever provider is selected
(`BrokerSummaryProvider`). See [Broker Summaries](#broker-summaries).
- **IDX (idx.co.id)** — the daily trading summary behind foreign flow
(`TradingSummaryProvider`). See [Foreign Flow](#foreign-flow).
- **IDX (idx.co.id)** — the daily trading summary behind `stocks flow
--source idx` (`TradingSummaryProvider`). See [Foreign Flow](#foreign-flow).
### MSN Endpoints
- `https://assets.msn.com/service/`: quotes, charts, equities, earnings,
@ -116,7 +116,13 @@ WIB as the fallback.
never summed as zero. A change to the rules gets a new method name.
### Foreign Flow
- `GET https://www.idx.co.id/primary/TradingSummary/GetStockSummary`
- The default source is IPOT: per session, the all-investor broker summary
gives the value traded and the foreign one (`fd=F`) gives foreign buying
(its footer), the foreign net, and selling (buying less the net), all
boards, through the broker summary cache. IPOT's foreign net matched IDX's
estimate within 0.3 B on BBCA 2026-10-12 to 16. An empty all-investor
summary counts as no session.
- `--source idx`: `GET https://www.idx.co.id/primary/TradingSummary/GetStockSummary`
`?length=9999&start=0&date=YYYYMMDD` answers one JSON row per stock for the
session: prices, volume, value, `ForeignBuy`/`ForeignSell` (shares) and
non-regular volume and value. `Volume`, `Value` and the foreign figures are

View file

@ -54,8 +54,8 @@ nix develop --command scripts/live-smoke.sh --compare ./old/bin/idx # A/B again
scripts/live-smoke.sh --dry-run
```
- `stocks`: every `stocks` command for one ticker (`broker`, `bandar` and
`flow` on the new binary only, so an older `--compare` build that predates
- `stocks`: every `stocks` command for one ticker (`broker`, `bandar`,
`flow` and `flow --source idx` on the new binary only, so an older `--compare` build that predates
them is fine), plus `compare BUMI,ADRO,AIMS`,
whose MSN key ratios contain non-finite values.
- `ownership`: `discover`, then imports the newest supported report into a temp

View file

@ -127,8 +127,10 @@ contract() {
echo '(.symbol | type == "string") and (.buyers | length > 0) and (.sellers | length > 0) and all(.buyers[]; (.code | type == "string") and (.value | type == "number")) and (.total_value | type == "number")' ;;
"stocks bandar "*)
echo 'type == "array" and length > 0 and all(.[]; (.method | type == "string") and (.tiers | length == 4) and (.imbalance_bps | type == "number"))' ;;
"stocks flow "*"--source idx"*)
echo '(.source == "idx") and (.days | length > 0) and all(.days[]; (.date | type == "string") and (.foreign_buy | type == "number") and (.foreign_sell | type == "number")) and (.foreign_net | type == "number")' ;;
"stocks flow "*)
echo '(.days | length > 0) and all(.days[]; (.date | type == "string") and (.foreign_buy | type == "number") and (.foreign_sell | type == "number")) and (.foreign_net | type == "number")' ;;
echo '(.source == "ipot") and (.days | length > 0) and all(.days[]; (.date | type == "string") and (.foreign_buy_value | type == "number") and (.foreign_sell_value | type == "number")) and (.foreign_net_value | type == "number")' ;;
"ownership discover"*)
echo 'type == "array" and length > 0 and (.[0].pdf_url | type == "string")' ;;
"ownership releases")
@ -229,10 +231,11 @@ if selected stocks; then
run_case compare "stocks compare $SYMBOL,BBRI"
# BUMI/ADRO/AIMS return non-finite MSN key ratios (Infinity/NaN).
run_case compare-nonfinite "stocks compare BUMI,ADRO,AIMS"
# Broker summaries (IPOT) and foreign flow (IDX, via curl-impersonate).
# Broker summaries and foreign flow (IPOT; --source idx via curl-impersonate).
run_new_case broker "stocks broker $SYMBOL"
run_new_case bandar "stocks bandar $SYMBOL,BBRI,TLKM"
run_new_case flow "stocks flow $SYMBOL --days 3"
run_new_case flow-idx "stocks flow $SYMBOL --days 3 --source idx"
fi
if selected ownership; then

View file

@ -433,7 +433,7 @@ Examples:
### `idx stocks flow`
```text
Foreign buying and selling of a stock, session by session, from IDX's daily trading summary: foreign buy, sell and net volume, the net value at the session's average price, and the share of value traded outside the regular board. End of day. Each session is one request to idx.co.id, which needs curl-impersonate.
Foreign buying and selling of a stock, session by session, end of day. By default from Indo Premier (IPOT): foreign buy, sell and net value across all boards and foreign investors' share of trading, two plain requests per session. `--source idx` reads IDX's own trading summary instead: exact foreign share counts, the close and the share traded outside the regular board, one request per session to idx.co.id, which needs curl-impersonate and a network IDX's Cloudflare lets through.
Usage: idx stocks flow [OPTIONS] <SYMBOL>
@ -462,6 +462,15 @@ Options:
--to <TO>
Last session of the range, YYYY-MM-DD. Default: today
--source <SOURCE>
Where the figures come from
Possible values:
- ipot: Indo Premier's broker summary: foreign buy, sell and net value across all boards, two plain requests per session. Figures of a million and more are rounded to one decimal
- idx: IDX's own trading summary: exact foreign share counts, close and the non-regular share, regular board. Needs curl-impersonate and a network IDX's Cloudflare lets through
[default: ipot]
-v, --verbose...
Increase verbosity (-v, -vv)
@ -478,7 +487,8 @@ Examples:
idx stocks flow BBCA
idx stocks flow BBCA --days 20
idx stocks flow BBCA --from 2026-10-01 --to 2026-10-16
idx -o json stocks flow BBCA | jq '.foreign_net'
idx stocks flow BBCA --source idx
idx -o json stocks flow BBCA | jq '.foreign_net_value'
```
### `idx stocks profile`

View file

@ -55,18 +55,25 @@ million and more to one decimal, and the output says so.
idx stocks flow BBCA
idx stocks flow BBCA --days 20
idx stocks flow BBCA --from 2026-10-01 --to 2026-10-16
idx stocks flow BBCA --source idx
```
What foreign investors bought and sold, session by session: buy, sell and
net volume in shares, the net value at each session's average price (an
estimate), and the share of all value traded outside the regular board.
Volume, value and the foreign figures are IDX's regular-board figures. The
footer gives the net over the range and foreign investors' share of trading.
Without dates it shows the latest 10 sessions (`--days`, at most 60).
What foreign investors bought and sold, session by session, with the totals
over the range. Without dates it shows the latest 10 sessions (`--days`, at
most 60).
The data is IDX's own daily trading summary. Each session is one request to
idx.co.id, cached afterwards for every stock, and needs
[curl-impersonate](/start/install/) to pass the site's bot protection.
By default the figures come from Indo Premier (IPOT), like `broker`: foreign
buy, sell and net value across all boards and foreign investors' share of
trading. It needs nothing installed, takes two requests per session, and
rounds figures of a million and more to one decimal.
`--source idx` reads IDX's own daily trading summary instead: exact foreign
share counts, the close and day change, and the share of value traded
outside the regular board. Those figures are the regular board, and values
are estimated at each session's average price. Each session is one request
to idx.co.id, cached for every stock, and needs
[curl-impersonate](/start/install/) and a network IDX's Cloudflare lets
through; even then it can answer 403.
## Bandarmology

View file

@ -65,12 +65,13 @@ Company data for one IDX ticker: profile, financial statements, earnings, analys
## `get_foreign_flow`
Foreign investors' buying and selling of one IDX ticker, session by session (oldest first), from IDX's daily trading summary: foreign buy, sell and net volume in shares, `foreign_net_value` (net shares at the session's average price, an estimate, IDR), `change_bps`, and `non_regular_bps` (share of all value traded outside the regular board). Volume, value and foreign figures are the regular board. Totals cover the whole range; `foreign_share_bps` is foreign investors' share of regular-board trading. End of day.
Foreign investors' buying and selling of one IDX ticker, session by session (oldest first), with range totals; end of day. With `source` "ipot" (default): `foreign_buy_value`, `foreign_sell_value` and `foreign_net_value` in IDR across all boards, `value` traded, and `foreign_share_bps` (foreign share of trading); share counts, close and `non_regular_bps` are null, and `rounded` = true means one decimal of precision. With "idx": exact foreign share counts on the regular board, values estimated at the session's average price, the close, `change_bps` and `non_regular_bps`.
| Input | Type | Required | Description |
| --- | --- | --- | --- |
| `days` | integer | no | Latest sessions (default 10, at most 60). Omit when giving from/to. |
| `from` | string | no | First session of a range, YYYY-MM-DD (at most 92 days). |
| `source` | `ipot` / `idx` | no | Where the figures come from (default "ipot"): "ipot" gives foreign buy, sell and net value across all boards; "idx" gives exact share counts, the close and the non-regular share, but needs curl-impersonate and may be blocked (HTTP 403). |
| `symbol` | string | yes | IDX ticker, e.g. "BBCA". |
| `to` | string | no | Last session of the range, YYYY-MM-DD (default today). |

View file

@ -74,13 +74,14 @@ idx version
## Helper tools
Everyday use needs nothing else: stock data with the default MSN provider,
price history, broker summaries and bandarmology, `ownership sync` and every
price history, broker summaries, bandarmology and foreign flow, `ownership sync`
and every
local ownership query. A few commands call external tools:
| Tool | Needed for |
| --- | --- |
| `curl_chrome*` from [curl-impersonate](https://github.com/lexiforest/curl-impersonate), or `IDX_CURL_IMPERSONATE_BIN` | `ownership discover` and `ownership import --url` (IDX sits behind Cloudflare), and Yahoo fundamentals with `IDX_PROVIDER=yahoo` |
| the full curl-impersonate set, with its `curl_safari*` and `curl_firefox*` profiles | `stocks flow`: IDX's Cloudflare rules pass only some browser fingerprints, and idx tries each installed profile |
| the full curl-impersonate set, with its `curl_safari*` and `curl_firefox*` profiles | `stocks flow --source idx`: IDX's Cloudflare rules pass only some browser fingerprints, and idx tries each installed profile (the default source, IPOT, needs nothing) |
| `mutool` from MuPDF | importing legacy PDF ownership reports |
## The MCP server