feat: bandarmology (idx stocks bandar, analyze_bandar)

`idx stocks bandar SYMBOL...` reads the broker summary: for the top 1, 3,
5 and 10 brokers per side, buy value minus sell value in basis points of
value traded, with a signal from big accumulation to big distribution on
the headline tier (--top, default 5). Several symbols give a screen ranked
by that figure (at most 100, one request each), which pipes from
`stocks screen`. The MCP server exposes it as analyze_bandar (20 symbols).

- analysis/bandar.rs is pure and versioned (`top_concentration_v1`):
  integer basis points, no figure missing counted as zero, buy/sell VWAP
  from exact averages, and the brokers on both sides listed because their
  gross figures overstate their direction.
- Thresholds (±5%/±15%) follow fzn0x/idx (MIT); docs call them heuristics.
- Invalid input fails a screen at once; other errors fail one symbol and
  are warned about on stderr.

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 04:55:25 +00:00
commit 970da26a0e
25 changed files with 1588 additions and 37 deletions

View file

@ -0,0 +1,360 @@
//! Bandarmology over a broker summary: is buying more concentrated in a few
//! brokers than selling? No network; see docs/ARCHITECTURE.md.
//!
//! The summary lists the top 10 buyers and the top 10 sellers by gross lots.
//! For N in 1, 3, 5 and 10 the top N buyers' value is set against the top N
//! sellers' value, as a share of the value traded:
//!
//! | top-N buy - sell value, % of value | signal |
//! |------------------------------------|--------------------|
//! | >= +15 | `big_accumulation` |
//! | +5 to +15 | `accumulation` |
//! | within +-5 | `neutral` |
//! | -15 to -5 | `distribution` |
//! | <= -15 | `big_distribution` |
//!
//! The thresholds follow fzn0x/idx (MIT), which set them against the 40 most
//! traded IDX stocks in October 2026. They are heuristics: a broker code is
//! not an investor, and gross figures overstate a broker that both bought
//! and sold (listed in `both_sides`).
use serde::{Deserialize, Serialize};
use crate::broker::types::{BrokerBoard, BrokerFlow, BrokerInvestor, BrokerSummary};
use crate::error::IdxError;
use chrono::NaiveDate;
/// Names the rules below; bumped when they change.
pub const METHOD: &str = "top_concentration_v1";
/// Tiers every report carries.
pub const TIERS: [u8; 4] = [1, 3, 5, 10];
/// Tier behind the headline signal unless asked otherwise.
pub const DEFAULT_TOP: u8 = 5;
/// Imbalance, in basis points of value traded, from which a tier reads
/// accumulation (or, negative, distribution).
pub const ACCUMULATION_BPS: i64 = 500;
/// Imbalance from which the reading is big.
pub const BIG_BPS: i64 = 1500;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BandarSignal {
BigAccumulation,
Accumulation,
Neutral,
Distribution,
BigDistribution,
}
impl BandarSignal {
pub fn as_str(self) -> &'static str {
match self {
Self::BigAccumulation => "big_accumulation",
Self::Accumulation => "accumulation",
Self::Neutral => "neutral",
Self::Distribution => "distribution",
Self::BigDistribution => "big_distribution",
}
}
pub fn from_imbalance_bps(bps: i64) -> Self {
match bps {
b if b >= BIG_BPS => Self::BigAccumulation,
b if b >= ACCUMULATION_BPS => Self::Accumulation,
b if b <= -BIG_BPS => Self::BigDistribution,
b if b <= -ACCUMULATION_BPS => Self::Distribution,
_ => Self::Neutral,
}
}
}
/// The top N buyers against the top N sellers. Every figure is `None` when
/// a broker in the tier lacks it, or when nothing traded.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BandarTier {
pub top: u8,
/// Value the top N buyers bought, whole IDR.
pub buy_value: Option<i64>,
/// Value the top N sellers sold, whole IDR.
pub sell_value: Option<i64>,
/// `buy_value - sell_value`, whole IDR.
pub imbalance_value: Option<i64>,
/// `imbalance_value` in basis points of the value traded.
pub imbalance_bps: Option<i64>,
/// The top N buyers' share of the value traded, basis points.
pub buy_share_bps: Option<i64>,
/// The top N sellers' share of the value traded, basis points.
pub sell_share_bps: Option<i64>,
/// Volume-weighted average price the top N buyers paid, whole IDR.
pub buy_average: Option<i64>,
/// Volume-weighted average price the top N sellers got, whole IDR.
pub sell_average: Option<i64>,
pub signal: Option<BandarSignal>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BandarReport {
pub symbol: String,
pub from: NaiveDate,
pub to: NaiveDate,
pub investor: BrokerInvestor,
pub board: BrokerBoard,
/// The rules applied, e.g. `top_concentration_v1`.
pub method: String,
/// Tier behind the headline `signal`.
pub top: u8,
/// The headline tier's signal; `None` when nothing traded or a figure
/// is missing.
pub signal: Option<BandarSignal>,
/// The headline tier's imbalance, basis points of value traded.
pub imbalance_bps: Option<i64>,
pub tiers: Vec<BandarTier>,
/// Value traded over the range, whole IDR.
pub total_value: Option<i64>,
/// Average traded price, whole IDR.
pub average_price: Option<i64>,
/// Foreign net value, whole IDR; `None` for a filtered summary.
pub foreign_net_value: Option<i64>,
/// `foreign_net_value` in basis points of the value traded.
pub foreign_net_bps: Option<i64>,
/// Brokers among the top 10 on both sides, whose gross figures overstate
/// which way they traded.
pub both_sides: Vec<String>,
/// The source rounded figures of a million and more to one decimal.
pub rounded: bool,
}
/// The headline tier must be one the report carries.
pub fn check_top(top: u8) -> Result<u8, IdxError> {
if TIERS.contains(&top) {
Ok(top)
} else {
Err(IdxError::InvalidInput(format!(
"top must be 1, 3, 5 or 10 (got {top})"
)))
}
}
/// `part` in basis points of `whole`; `None` without a positive whole.
fn bps(part: i64, whole: Option<i64>) -> Option<i64> {
let whole = whole.filter(|whole| *whole > 0)?;
i64::try_from((i128::from(part) * 10_000).div_euclid(i128::from(whole))).ok()
}
/// Sum of `field` over `flows`; `None` when any flow lacks it.
fn sum(flows: &[BrokerFlow], field: fn(&BrokerFlow) -> Option<i64>) -> Option<i64> {
flows
.iter()
.try_fold(0_i64, |total, flow| total.checked_add(field(flow)?))
}
/// Value over shares, the shares taken from each broker's value and its
/// exact published average (lots of a million and more are rounded,
/// averages never are).
fn vwap(flows: &[BrokerFlow]) -> Option<i64> {
let (mut value, mut shares) = (0.0_f64, 0.0_f64);
for flow in flows {
let (flow_value, average) = (flow.value?, flow.average_price?);
if average <= 0 {
return None;
}
value += flow_value as f64;
shares += flow_value as f64 / average as f64;
}
(shares > 0.0).then(|| (value / shares).round() as i64)
}
pub fn bandar_tier(summary: &BrokerSummary, top: u8) -> BandarTier {
let n = usize::from(top);
let buyers = &summary.buyers[..n.min(summary.buyers.len())];
let sellers = &summary.sellers[..n.min(summary.sellers.len())];
let buy_value = sum(buyers, |flow| flow.value);
let sell_value = sum(sellers, |flow| flow.value);
let imbalance_value = buy_value
.zip(sell_value)
.and_then(|(buy, sell)| buy.checked_sub(sell));
let imbalance_bps = imbalance_value.and_then(|value| bps(value, summary.total_value));
BandarTier {
top,
buy_value,
sell_value,
imbalance_value,
imbalance_bps,
buy_share_bps: buy_value.and_then(|value| bps(value, summary.total_value)),
sell_share_bps: sell_value.and_then(|value| bps(value, summary.total_value)),
buy_average: vwap(buyers),
sell_average: vwap(sellers),
signal: imbalance_bps.map(BandarSignal::from_imbalance_bps),
}
}
/// The full reading of one summary, headlined by the top `top` tier (one of
/// [`TIERS`]; see [`check_top`]).
pub fn analyze_bandar(summary: &BrokerSummary, top: u8) -> BandarReport {
let tiers: Vec<BandarTier> = TIERS.iter().map(|n| bandar_tier(summary, *n)).collect();
let headline = tiers
.iter()
.find(|tier| tier.top == top)
.cloned()
.unwrap_or_else(|| bandar_tier(summary, top));
let mut both_sides: Vec<String> = summary
.buyers
.iter()
.filter(|buyer| {
summary
.sellers
.iter()
.any(|seller| seller.code == buyer.code)
})
.map(|buyer| buyer.code.clone())
.collect();
both_sides.sort_unstable();
BandarReport {
symbol: summary.symbol.clone(),
from: summary.from,
to: summary.to,
investor: summary.investor,
board: summary.board,
method: METHOD.to_string(),
top,
signal: headline.signal,
imbalance_bps: headline.imbalance_bps,
tiers,
total_value: summary.total_value,
average_price: summary.average_price,
foreign_net_value: summary.foreign_net_value,
foreign_net_bps: summary
.foreign_net_value
.and_then(|value| bps(value, summary.total_value)),
both_sides,
rounded: summary.rounded,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn flow(code: &str, value: Option<i64>, average: i64) -> BrokerFlow {
BrokerFlow {
rank: 0,
code: code.to_string(),
name: None,
kind: None,
lots: value.map(|value| value / average / 100),
value,
average_price: Some(average),
}
}
fn summary(buyers: Vec<BrokerFlow>, sellers: Vec<BrokerFlow>, total: i64) -> BrokerSummary {
let day = NaiveDate::from_ymd_opt(2026, 10, 16).unwrap();
BrokerSummary {
symbol: "TEST.JK".to_string(),
from: day,
to: day,
investor: BrokerInvestor::All,
board: BrokerBoard::All,
buyers,
sellers,
total_value: Some(total),
total_lots: None,
average_price: None,
foreign_net_value: Some(-total / 10),
rounded: false,
}
}
#[test]
fn test_bandar_signal_thresholds_are_inclusive() {
use BandarSignal::*;
for (bps, signal) in [
(1500, BigAccumulation),
(1499, Accumulation),
(500, Accumulation),
(499, Neutral),
(-499, Neutral),
(-500, Distribution),
(-1499, Distribution),
(-1500, BigDistribution),
] {
assert_eq!(BandarSignal::from_imbalance_bps(bps), signal, "{bps}");
}
}
#[test]
fn test_analyze_bandar_weighs_top_buyers_against_top_sellers() {
let s = summary(
vec![
flow("AA", Some(400), 100),
flow("BB", Some(200), 100),
flow("CC", Some(100), 50),
],
vec![
flow("DD", Some(300), 110),
flow("AA", Some(100), 110),
flow("EE", Some(50), 110),
],
1000,
);
let report = analyze_bandar(&s, 3);
let top1 = &report.tiers[0];
assert_eq!((top1.top, top1.imbalance_value), (1, Some(100)));
assert_eq!(top1.imbalance_bps, Some(1000));
assert_eq!(top1.signal, Some(BandarSignal::Accumulation));
let top3 = &report.tiers[1];
assert_eq!((top3.buy_value, top3.sell_value), (Some(700), Some(450)));
assert_eq!(
(top3.buy_share_bps, top3.sell_share_bps),
(Some(7000), Some(4500))
);
assert_eq!(top3.imbalance_bps, Some(2500));
// 700 IDR over 4 + 2 + 2 shares.
assert_eq!(top3.buy_average, Some(88));
assert_eq!(top3.sell_average, Some(110));
// Short lists: every broker on a side is listed, so the sums hold.
assert_eq!(report.tiers[3].imbalance_value, Some(250));
assert_eq!(
(report.top, report.signal),
(3, Some(BandarSignal::BigAccumulation))
);
assert_eq!(report.imbalance_bps, Some(2500));
assert_eq!(report.foreign_net_bps, Some(-1000));
assert_eq!(report.both_sides, vec!["AA".to_string()]);
assert_eq!(report.method, METHOD);
}
#[test]
fn test_analyze_bandar_never_counts_a_missing_figure_as_zero() {
let s = summary(
vec![flow("AA", Some(400), 100), flow("BB", None, 100)],
vec![flow("DD", Some(300), 100)],
1000,
);
let report = analyze_bandar(&s, 3);
assert_eq!(report.tiers[0].imbalance_bps, Some(1000));
let top3 = &report.tiers[1];
assert_eq!(
(top3.buy_value, top3.imbalance_bps, top3.signal),
(None, None, None)
);
assert_eq!(top3.buy_average, None);
assert_eq!(report.signal, None);
}
#[test]
fn test_analyze_bandar_without_trades_has_no_signal() {
let report = analyze_bandar(&summary(vec![], vec![], 0), DEFAULT_TOP);
assert!(report.tiers.iter().all(|tier| tier.signal.is_none()));
assert_eq!((report.signal, report.imbalance_bps), (None, None));
assert_eq!(report.tiers[2].buy_value, Some(0));
}
#[test]
fn test_check_top_allows_only_report_tiers() {
assert_eq!(check_top(5).unwrap(), 5);
assert!(matches!(check_top(4), Err(IdxError::InvalidInput(_))));
}
}

View file

@ -1,3 +1,4 @@
pub mod bandar;
pub mod fundamental;
pub mod report;
pub mod signals;
@ -8,9 +9,11 @@ use serde::de::DeserializeOwned;
use crate::api::history_provider;
use crate::api::types::{Fundamentals, Interval, Period};
use crate::broker::types::{BrokerBoard, BrokerInvestor, BrokerRange};
use crate::client::{CachePolicy, CacheSpec, Fetched, Idx, cached_fetch};
use crate::error::IdxError;
use crate::market::HistorySource;
use bandar::{BandarReport, analyze_bandar, check_top};
use fundamental::{
FundamentalReport, GrowthReport, RiskReport, ValuationReport, analyze_fundamental,
analyze_growth, analyze_risk, analyze_valuation,
@ -28,7 +31,69 @@ pub struct Comparison {
pub failures: Vec<(String, IdxError)>,
}
/// Bandar readings for several symbols, strongest accumulation first;
/// symbols that failed are listed with their error.
pub struct BandarScreen {
pub reports: Vec<Fetched<BandarReport>>,
pub failures: Vec<(String, IdxError)>,
}
/// The sessions and trades a bandar reading covers.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BandarQuery {
pub range: BrokerRange,
pub investor: BrokerInvestor,
pub board: BrokerBoard,
/// Headline tier, one of `bandar::TIERS`.
pub top: u8,
}
impl Analysis<'_> {
/// Bandarmology over the broker summary `query` selects.
pub fn bandar(
&self,
symbol: &str,
query: &BandarQuery,
policy: CachePolicy,
) -> Result<Fetched<BandarReport>, IdxError> {
let top = check_top(query.top)?;
let summary =
self.idx
.broker()
.fetch(symbol, query.range, query.investor, query.board, policy)?;
Ok(summary.map(|summary| analyze_bandar(&summary, top)))
}
/// [`Self::bandar`] for each symbol, ranked by the headline imbalance.
/// Invalid input fails the whole screen; other errors fail one symbol.
pub fn bandar_screen<S: AsRef<str>>(
&self,
symbols: &[S],
query: &BandarQuery,
policy: CachePolicy,
) -> Result<BandarScreen, IdxError> {
check_top(query.top)?;
let mut reports = Vec::new();
let mut failures = Vec::new();
for symbol in symbols {
let resolved = self.idx.resolve(symbol.as_ref())?;
match self.bandar(&resolved, query, policy) {
Ok(report) => reports.push(report),
Err(err @ IdxError::InvalidInput(_)) => return Err(err),
Err(err) => failures.push((resolved, err)),
}
}
if reports.is_empty() {
return Err(failures
.pop()
.map(|(_, err)| err)
.unwrap_or_else(|| IdxError::InvalidInput("no symbols to screen".to_string())));
}
// Strongest accumulation first; readings without an imbalance last.
reports.sort_by_key(|report| std::cmp::Reverse(report.data.imbalance_bps));
Ok(BandarScreen { reports, failures })
}
/// Technical analysis over one year of daily history from `source`
/// (see `Market::history_source`).
pub fn technical(

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/16/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/16/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-bumn">CC</span></td>
<td align="right"><span class="text-up">1.9 M</span></td>
<td align="right"><span class="text-up">504.3 B</span></td>
<td align="right"><span class="text-up">2,709 </span></td>
<td align="center">1</td>
<td align="center"><span class="text-foreign">AK</span></td>
<td align="right"><span class="text-down">3.7 M</span></td>
<td align="right"><span class="text-down">1.0 T</span></td>
<td align="right"><span class="text-down">2,716 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">XL</span></td>
<td align="right"><span class="text-up">1.6 M</span></td>
<td align="right"><span class="text-up">436.0 B</span></td>
<td align="right"><span class="text-up">2,720 </span></td>
<td align="center">2</td>
<td align="center"><span class="text-foreign">KZ</span></td>
<td align="right"><span class="text-down">1.8 M</span></td>
<td align="right"><span class="text-down">481.4 B</span></td>
<td align="right"><span class="text-down">2,672 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">AK</span></td>
<td align="right"><span class="text-up">1.2 M</span></td>
<td align="right"><span class="text-up">322.4 B</span></td>
<td align="right"><span class="text-up">2,719 </span></td>
<td align="center">3</td>
<td align="center"><span class="text-bumn">CC</span></td>
<td align="right"><span class="text-down">1.3 M</span></td>
<td align="right"><span class="text-down">358.2 B</span></td>
<td align="right"><span class="text-down">2,717 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">YP</span></td>
<td align="right"><span class="text-up">1.0 M</span></td>
<td align="right"><span class="text-up">281.3 B</span></td>
<td align="right"><span class="text-up">2,694 </span></td>
<td align="center">4</td>
<td align="center"><span class="text-foreign">BK</span></td>
<td align="right"><span class="text-down">933,299 </span></td>
<td align="right"><span class="text-down">253.5 B</span></td>
<td align="right"><span class="text-down">2,716 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">SQ</span></td>
<td align="right"><span class="text-up">712,722 </span></td>
<td align="right"><span class="text-up">193.1 B</span></td>
<td align="right"><span class="text-up">2,709 </span></td>
<td align="center">5</td>
<td align="center"><span class="text-local">XL</span></td>
<td align="right"><span class="text-down">697,845 </span></td>
<td align="right"><span class="text-down">189.9 B</span></td>
<td align="right"><span class="text-down">2,721 </span></td>
</tr>
<tr>
<td align="center"><span class="text-bumn">OD</span></td>
<td align="right"><span class="text-up">518,938 </span></td>
<td align="right"><span class="text-up">140.6 B</span></td>
<td align="right"><span class="text-up">2,709 </span></td>
<td align="center">6</td>
<td align="center"><span class="text-foreign">ZP</span></td>
<td align="right"><span class="text-down">477,399 </span></td>
<td align="right"><span class="text-down">129.4 B</span></td>
<td align="right"><span class="text-down">2,711 </span></td>
</tr>
<tr>
<td align="center"><span class="text-bumn">NI</span></td>
<td align="right"><span class="text-up">496,800 </span></td>
<td align="right"><span class="text-up">133.9 B</span></td>
<td align="right"><span class="text-up">2,695 </span></td>
<td align="center">7</td>
<td align="center"><span class="text-foreign">YU</span></td>
<td align="right"><span class="text-down">447,840 </span></td>
<td align="right"><span class="text-down">120.9 B</span></td>
<td align="right"><span class="text-down">2,700 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">YU</span></td>
<td align="right"><span class="text-up">420,264 </span></td>
<td align="right"><span class="text-up">112.3 B</span></td>
<td align="right"><span class="text-up">2,672 </span></td>
<td align="center">8</td>
<td align="center"><span class="text-foreign">YP</span></td>
<td align="right"><span class="text-down">431,663 </span></td>
<td align="right"><span class="text-down">117.6 B</span></td>
<td align="right"><span class="text-down">2,725 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">PD</span></td>
<td align="right"><span class="text-up">403,468 </span></td>
<td align="right"><span class="text-up">109.5 B</span></td>
<td align="right"><span class="text-up">2,713 </span></td>
<td align="center">9</td>
<td align="center"><span class="text-bumn">NI</span></td>
<td align="right"><span class="text-down">234,641 </span></td>
<td align="right"><span class="text-down">63.1 B</span></td>
<td align="right"><span class="text-down">2,690 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">XC</span></td>
<td align="right"><span class="text-up">381,871 </span></td>
<td align="right"><span class="text-up">103.7 B</span></td>
<td align="right"><span class="text-up">2,717 </span></td>
<td align="center">10</td>
<td align="center"><span class="text-local">XA</span></td>
<td align="right"><span class="text-down">217,888 </span></td>
<td align="right"><span class="text-down">59.1 B</span></td>
<td align="right"><span class="text-down">2,714 </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 : 3.3 T</span>
<span class="fsize12 ml5 mr5">F. NVal : -1.1 T</span>
<span class="fsize12 ml5 mr5">T.Lot : 12.2 M</span>
<span class="fsize12 ml5 mr5">Avg : 2,708 </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/16/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/16/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">1.9 M</span></td>
<td align="right"><span class="text-up">381.6 B</span></td>
<td align="right"><span class="text-up">1,960 </span></td>
<td align="center">1</td>
<td align="center"><span class="text-foreign">KZ</span></td>
<td align="right"><span class="text-down">3.7 M</span></td>
<td align="right"><span class="text-down">721.6 B</span></td>
<td align="right"><span class="text-down">1,933 </span></td>
</tr>
<tr>
<td align="center"><span class="text-bumn">CC</span></td>
<td align="right"><span class="text-up">1.5 M</span></td>
<td align="right"><span class="text-up">292.8 B</span></td>
<td align="right"><span class="text-up">1,932 </span></td>
<td align="center">2</td>
<td align="center"><span class="text-bumn">CC</span></td>
<td align="right"><span class="text-down">1.8 M</span></td>
<td align="right"><span class="text-down">353.9 B</span></td>
<td align="right"><span class="text-down">1,933 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">BK</span></td>
<td align="right"><span class="text-up">1.4 M</span></td>
<td align="right"><span class="text-up">274.7 B</span></td>
<td align="right"><span class="text-up">1,917 </span></td>
<td align="center">3</td>
<td align="center"><span class="text-foreign">BK</span></td>
<td align="right"><span class="text-down">1.6 M</span></td>
<td align="right"><span class="text-down">312.2 B</span></td>
<td align="right"><span class="text-down">1,926 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">RX</span></td>
<td align="right"><span class="text-up">1.0 M</span></td>
<td align="right"><span class="text-up">200.0 B</span></td>
<td align="right"><span class="text-up">1,933 </span></td>
<td align="center">4</td>
<td align="center"><span class="text-foreign">ZP</span></td>
<td align="right"><span class="text-down">1.0 M</span></td>
<td align="right"><span class="text-down">198.1 B</span></td>
<td align="right"><span class="text-down">1,926 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">ZP</span></td>
<td align="right"><span class="text-up">992,588 </span></td>
<td align="right"><span class="text-up">192.3 B</span></td>
<td align="right"><span class="text-up">1,937 </span></td>
<td align="center">5</td>
<td align="center"><span class="text-foreign">AK</span></td>
<td align="right"><span class="text-down">704,361 </span></td>
<td align="right"><span class="text-down">136.7 B</span></td>
<td align="right"><span class="text-down">1,941 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">YU</span></td>
<td align="right"><span class="text-up">944,581 </span></td>
<td align="right"><span class="text-up">180.4 B</span></td>
<td align="right"><span class="text-up">1,910 </span></td>
<td align="center">6</td>
<td align="center"><span class="text-local">MG</span></td>
<td align="right"><span class="text-down">529,257 </span></td>
<td align="right"><span class="text-down">101.4 B</span></td>
<td align="right"><span class="text-down">1,916 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">AK</span></td>
<td align="right"><span class="text-up">715,278 </span></td>
<td align="right"><span class="text-up">138.2 B</span></td>
<td align="right"><span class="text-up">1,932 </span></td>
<td align="center">7</td>
<td align="center"><span class="text-foreign">YU</span></td>
<td align="right"><span class="text-down">324,973 </span></td>
<td align="right"><span class="text-down">62.5 B</span></td>
<td align="right"><span class="text-down">1,924 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">MG</span></td>
<td align="right"><span class="text-up">528,650 </span></td>
<td align="right"><span class="text-up">100.6 B</span></td>
<td align="right"><span class="text-up">1,903 </span></td>
<td align="center">8</td>
<td align="center"><span class="text-bumn">NI</span></td>
<td align="right"><span class="text-down">208,165 </span></td>
<td align="right"><span class="text-down">39.8 B</span></td>
<td align="right"><span class="text-down">1,911 </span></td>
</tr>
<tr>
<td align="center"><span class="text-local">XL</span></td>
<td align="right"><span class="text-up">331,760 </span></td>
<td align="right"><span class="text-up">64.1 B</span></td>
<td align="right"><span class="text-up">1,931 </span></td>
<td align="center">9</td>
<td align="center"><span class="text-local">XL</span></td>
<td align="right"><span class="text-down">173,939 </span></td>
<td align="right"><span class="text-down">33.7 B</span></td>
<td align="right"><span class="text-down">1,937 </span></td>
</tr>
<tr>
<td align="center"><span class="text-foreign">YP</span></td>
<td align="right"><span class="text-up">234,664 </span></td>
<td align="right"><span class="text-up">44.9 B</span></td>
<td align="right"><span class="text-up">1,913 </span></td>
<td align="center">10</td>
<td align="center"><span class="text-foreign">YP</span></td>
<td align="right"><span class="text-down">168,848 </span></td>
<td align="right"><span class="text-down">32.7 B</span></td>
<td align="right"><span class="text-down">1,938 </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 : 2.2 T</span>
<span class="fsize12 ml5 mr5">F. NVal : -90.2 B</span>
<span class="fsize12 ml5 mr5">T.Lot : 11.3 M</span>
<span class="fsize12 ml5 mr5">Avg : 1,931 </span>
</div>
</th>
</tr>
</tfoot>
</table>
</div>
<!-- =======================
======SCRIPT MODULE=========
============================-->
<script src="../../../../module/saham/include/js/data-brokersummary.js"></script>