feat(cli): agent-native output, stdin symbols, and exit-code classes

Phase 1 of docs/DESIGN.md:

- Output format resolves -o > IDX_OUTPUT > general.output > auto; auto
  is a table on a terminal and JSON when stdout is piped or captured.
  `config init` now writes output = "auto".
- JSON is pretty only on a terminal and compact otherwise. New `-o
  jsonl` prints one array item per line with the same key order.
- No ANSI color when stdout is not a terminal (direct owo-colors calls
  now go through output::style).
- `stocks quote -` and `stocks compare -` read symbols from stdin.
- Exit codes by class: 2 invalid input/config, 3 not found or cache
  miss, 4 upstream unavailable, 5 unsupported, 1 other.

Golden changes are limited to those documented kinds and were verified
mechanically: JSON goldens are semantically identical, tables differ
only by removed ANSI codes and the column padding comfy-table had
reserved for them (cell contents identical), exit codes match each
error's class, and help/completions only gain the new --output values.
17 new golden cases cover auto, jsonl, stdin, and the new errors.

tests/cli.rs now pins IDX_OUTPUT=table in its harness (its tests assert
table text and run piped) and parses discover JSON instead of matching
pretty-printed whitespace.

Live A/B against main: 16/16 stocks cases identical; ownership discover
got IDX 403 on both binaries, now exiting 4 instead of 1 as intended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
hermes 2026-10-14 08:52:13 +00:00
commit 22b6dabaeb
119 changed files with 920 additions and 1896 deletions

View file

@ -32,7 +32,7 @@ clap = { version = "4", features = ["derive"] }
clap_complete = "4"
ureq = { version = "3", features = ["json", "cookies"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_json = { version = "1", features = ["raw_value"] }
comfy-table = "7"
owo-colors = "4"
toml = "0.8"

View file

@ -112,7 +112,11 @@ idx ownership changes --from 2026-07-31 --to 2026-08-31
### Output
- Human-readable tables on stdout, JSON with `-o json`, errors on stderr
- Tables when you run idx in a terminal; compact JSON when its output is piped
or captured, so scripts and AI agents get structured data with no flag
- `-o table|json|jsonl` (or `IDX_OUTPUT`, or `general.output` in the config)
overrides that; `jsonl` prints one array item per line
- Errors go to stderr, as one JSON object in JSON modes
## Configuration
@ -133,12 +137,26 @@ Precedence order:
3. Config file
4. Built-in defaults
## Agent-friendly usage
## Scripting and agents
- Use `idx --help` and subcommand help for discoverability
- Use `-o json` / `--output json` for structured output
idx composes with `jq`, Python, and other shell tools:
```bash
idx stocks quote BBCA BBRI | jq -r '.[] | "\(.symbol) \(.price)"'
idx -o jsonl stocks history BBCA --period 1y | tail -5
idx stocks screen --filter low-pe | jq -r '.[].symbol' | idx stocks compare -
```
- stdout carries only data; warnings and errors go to stderr
- `-` reads symbols from stdin for `stocks quote` and `stocks compare`
- No color codes when output is not a terminal
- Exit codes: `0` success, `2` invalid input or config, `3` not found or no
cached data, `4` upstream unavailable (retry later), `5` unsupported, `1` other
- `idx completions <shell>` generates shell completions
If you ran `idx config init` before v0.3, your config pins `output = "table"`;
change it to `auto` with `idx config set general.output auto`.
## Documentation
- [docs/INSTALL.md](docs/INSTALL.md): install script options

View file

@ -144,11 +144,21 @@ CLI flags > environment variables > config file > defaults
## Error Strategy
- `IdxError` enum (thiserror) with structured error codes
- Table mode: human-readable error on stderr
- JSON mode: `{"error": true, "code": "...", "message": "..."}`
- Exit code 0 on success, non-zero on failure
- JSON and JSONL modes: `{"error": true, "code": "...", "message": "..."}` on stderr, stdout empty
- Exit codes by class: `0` success, `2` `INVALIDINPUT`/`CONFIGERROR` and clap usage
errors, `3` `SYMBOLNOTFOUND`/`NOMARKETDATA`/`CACHEMISS`, `4` `HTTP`/`RATELIMITED`/
`PROVIDERUNAVAILABLE`/`OFFLINE`/`AUTHERROR`, `5` `UNSUPPORTED`, `1` everything else
- A totally failed `stocks compare` emits one structured error in JSON mode; per-symbol warnings are retained for partial success, not printed ahead of a total-failure error.
## CLI Output and Offline Cache
- Output format resolves as `-o` flag > `IDX_OUTPUT` > `general.output` > `auto`.
`auto` is a table when stdout is a terminal and JSON otherwise
(`std::io::IsTerminal`).
- JSON is pretty-printed only on a terminal; piped output is compact. `jsonl`
prints each array item on its own line with the same key order as `json`,
and a single object as one line.
- ANSI styling goes through `output::style` and is disabled when stdout is not a
terminal or color is turned off.
- `version`, `cache info`, `cache clear`, and `ownership resolve map|merge` emit
JSON objects when JSON output is selected.
- `stocks quote` and `stocks compare` require at least one symbol.

View file

@ -1,6 +1,6 @@
# System Design: idx-core and its frontends
**Status:** Accepted 2026-10-14. Implementation on `refactor/idx-core`; nothing implemented yet.
**Status:** Accepted 2026-10-14. Implementation on `refactor/idx-core`; phases 0-1 done.
## Goal
@ -164,8 +164,8 @@ is another 20-30% smaller than pretty JSON.
Already true.
- No ANSI color when stdout is not a terminal. Today some table headers emit
bold codes even when piped.
- Commands that take symbols accept `-` to read them from stdin, one per line or
whitespace-separated:
- `stocks quote` and `stocks compare` accept `-` to read symbols from stdin,
separated by whitespace or commas:
`idx stocks screen --filter low-pe | jq -r '.[].symbol' | idx stocks compare -`
- Exit codes by error class (today every error exits `1`):
@ -175,7 +175,7 @@ is another 20-30% smaller than pretty JSON.
| 1 | other failure | `IO`, `DATABASEERROR`, `PARSEERROR`, ... |
| 2 | invalid usage or input | clap errors, `INVALIDINPUT`, `CONFIGERROR` |
| 3 | not found or no data | `SYMBOLNOTFOUND`, `NOMARKETDATA`, `CACHEMISS` |
| 4 | upstream unavailable, retry later | `HTTP`, `RATELIMITED`, `PROVIDERUNAVAILABLE`, `OFFLINE` |
| 4 | upstream unavailable, retry later | `HTTP`, `RATELIMITED`, `PROVIDERUNAVAILABLE`, `OFFLINE`, `AUTHERROR` |
| 5 | unsupported | `UNSUPPORTED` |
- JSON keeps today's top-level shape: no `{data, meta}` wrapper, so `jq` paths

View file

@ -25,7 +25,7 @@ pub fn handle(cmd: &CacheCmd, format: &OutputFormat) -> Result<(), IdxError> {
match &cmd.command {
CacheSubcommand::Info => {
let info = cache.info()?;
if matches!(format, OutputFormat::Json) {
if format.is_json() {
return json::print_json(&serde_json::json!({
"path": info.path,
"files": info.files,
@ -52,7 +52,7 @@ pub fn handle(cmd: &CacheCmd, format: &OutputFormat) -> Result<(), IdxError> {
}
CacheSubcommand::Clear => {
let (removed, failed) = cache.clear()?;
if matches!(format, OutputFormat::Json) {
if format.is_json() {
return json::print_json(&serde_json::json!({
"removed": removed,
"failed": failed,

View file

@ -6,7 +6,7 @@ pub mod stocks;
use clap::{Parser, Subcommand, ValueEnum};
use crate::output::OutputFormat;
use crate::output::OutputSetting;
#[derive(Debug, Clone, ValueEnum)]
pub enum Shell {
@ -23,8 +23,14 @@ pub enum Shell {
long_about = "idx-cli is a Rust command-line tool for Indonesian stock market (IDX) analysis.\nIt supports quote lookup, historical data, local caching, and output formats for both humans and AI agents."
)]
pub struct Cli {
#[arg(short, long, value_enum, global = true, help = "Output format")]
pub output: Option<OutputFormat>,
#[arg(
short,
long,
value_enum,
global = true,
help = "Output format [default: auto = table on a terminal, json when piped]"
)]
pub output: Option<OutputSetting>,
#[arg(long, global = true, help = "Disable colored output")]
pub no_color: bool,
#[arg(short, long, global = true, help = "Suppress non-essential output")]

View file

@ -2,16 +2,15 @@ use std::collections::HashSet;
use std::fs;
use std::path::{Path, PathBuf};
use crate::output::style::Paint;
use chrono::Utc;
use clap::{Args, Subcommand};
use comfy_table::{Cell, ContentArrangement, Table, presets::UTF8_FULL};
use owo_colors::OwoColorize;
use serde::Serialize;
use sha2::{Digest, Sha256};
use crate::config::IdxConfig;
use crate::error::IdxError;
use crate::output::OutputFormat;
use crate::output::json;
use crate::output::table::format_idr;
use crate::ownership::types::{
@ -219,7 +218,7 @@ fn handle_sync(args: &SyncArgs, config: &IdxConfig) -> Result<(), IdxError> {
let db_path = db::db_path(config)?;
let result = snapshot::sync_snapshot(&manifest_source, &db_path, args.force)?;
if matches!(config.output, OutputFormat::Json) {
if config.output.is_json() {
return json::print_json(&result);
}
@ -269,7 +268,7 @@ fn handle_discover(args: &DiscoverArgs, config: &IdxConfig) -> Result<(), IdxErr
let family = parse_discovery_family(&args.family)?;
let reports = remote::discover_idx_ownership_reports(family, args.limit)?;
if matches!(config.output, OutputFormat::Json) {
if config.output.is_json() {
return json::print_json(&reports);
}
@ -337,7 +336,7 @@ fn handle_ticker(args: &TickerArgs, config: &IdxConfig) -> Result<(), IdxError>
)));
}
if matches!(config.output, OutputFormat::Json) {
if config.output.is_json() {
return json::print_json(&data);
}
@ -407,7 +406,7 @@ fn handle_entity(args: &EntityArgs, config: &IdxConfig) -> Result<(), IdxError>
let data = db::query_entity_holdings(&conn, entity.0)?;
if matches!(config.output, OutputFormat::Json) {
if config.output.is_json() {
return json::print_json(&data);
}
@ -440,7 +439,7 @@ fn handle_search(args: &SearchArgs, config: &IdxConfig) -> Result<(), IdxError>
let conn = db::open_db(config)?;
let entities = search::fts_search(&conn, &args.query, args.limit)?;
if matches!(config.output, OutputFormat::Json) {
if config.output.is_json() {
return json::print_json(&entities);
}
@ -475,7 +474,7 @@ fn handle_cross_holders(args: &CrossHolderArgs, config: &IdxConfig) -> Result<()
let conn = db::open_db(config)?;
let rows = db::query_cross_holders(&conn, args.min_tickers, args.top)?;
if matches!(config.output, OutputFormat::Json) {
if config.output.is_json() {
return json::print_json(&rows);
}
@ -509,7 +508,7 @@ fn handle_concentration(args: &ConcentrationArgs, config: &IdxConfig) -> Result<
let rows = db::query_concentration(&conn, &by, args.top)?;
if matches!(config.output, OutputFormat::Json) {
if config.output.is_json() {
return json::print_json(&rows);
}
@ -551,14 +550,14 @@ fn handle_flow(args: &FlowArgs, config: &IdxConfig) -> Result<(), IdxError> {
let flow = db::query_bing_flow(&conn, ticker_id)?;
let Some(flow) = flow else {
if matches!(config.output, OutputFormat::Json) {
if config.output.is_json() {
return json::print_json(&Option::<crate::ownership::types::InstitutionalFlow>::None);
}
println!("No institutional flow data available for {symbol}.");
return Ok(());
};
if matches!(config.output, OutputFormat::Json) {
if config.output.is_json() {
return json::print_json(&flow);
}
@ -599,7 +598,7 @@ fn handle_graph(args: &GraphArgs, config: &IdxConfig) -> Result<(), IdxError> {
let conn = db::open_db(config)?;
let (nodes, edges) = graph::query_ownership_graph(&conn, &args.root, args.depth)?;
if matches!(config.output, OutputFormat::Json) {
if config.output.is_json() {
#[derive(Serialize)]
struct GraphOutput<'a> {
nodes: &'a [crate::ownership::types::GraphNode],
@ -630,7 +629,7 @@ fn handle_changes(args: &ChangesArgs, config: &IdxConfig) -> Result<(), IdxError
let conn = db::open_db(config)?;
let changes = db::query_changes(&conn, &args.from, &args.to)?;
if matches!(config.output, OutputFormat::Json) {
if config.output.is_json() {
return json::print_json(&changes);
}
@ -679,7 +678,7 @@ fn handle_resolve(args: &ResolveArgs, config: &IdxConfig) -> Result<(), IdxError
ResolveCommand::Unresolved { limit } => {
let rows = db::list_unresolved(&conn, *limit)?;
if matches!(config.output, OutputFormat::Json) {
if config.output.is_json() {
return json::print_json(&rows);
}
@ -718,7 +717,7 @@ fn handle_resolve(args: &ResolveArgs, config: &IdxConfig) -> Result<(), IdxError
}
ResolveCommand::Map { alias, entity } => {
db::manual_map(&conn, alias, entity)?;
if matches!(config.output, OutputFormat::Json) {
if config.output.is_json() {
return json::print_json(&serde_json::json!({
"action": "mapped",
"alias": alias,
@ -753,7 +752,7 @@ fn handle_resolve(args: &ResolveArgs, config: &IdxConfig) -> Result<(), IdxError
db::merge_entities(&conn, *keep, *merge)?;
if matches!(config.output, OutputFormat::Json) {
if config.output.is_json() {
return json::print_json(&serde_json::json!({
"action": "merged",
"keep": keep,
@ -813,7 +812,7 @@ fn handle_import(args: &ImportArgs, config: &IdxConfig) -> Result<(), IdxError>
source_url: import_input.source_url,
};
if matches!(config.output, OutputFormat::Json) {
if config.output.is_json() {
return json::print_json(&result);
}
@ -897,7 +896,7 @@ fn handle_import(args: &ImportArgs, config: &IdxConfig) -> Result<(), IdxError>
source_url: release.source_url.clone(),
};
if matches!(config.output, OutputFormat::Json) {
if config.output.is_json() {
return json::print_json(&result);
}
@ -915,7 +914,7 @@ fn handle_releases(config: &IdxConfig) -> Result<(), IdxError> {
let conn = db::open_db(config)?;
let releases = db::query_releases(&conn)?;
if matches!(config.output, OutputFormat::Json) {
if config.output.is_json() {
return json::print_json(&releases);
}

View file

@ -1,4 +1,5 @@
use std::cmp::Ordering;
use std::io::Read;
use clap::{Args, Subcommand, ValueEnum};
use serde::{Serialize, de::DeserializeOwned};
@ -50,6 +51,34 @@ const SCREENER_FILTERS: &[&str] = &[
const SCREENER_REGIONS: &[&str] = &["id", "us", "sg", "hk", "jp"];
/// Splits comma-separated symbol arguments; `-` reads symbols from stdin,
/// separated by whitespace or commas.
fn expand_symbols(args: &[String]) -> Result<Vec<String>, IdxError> {
let mut symbols = Vec::new();
for arg in args {
if arg == "-" {
let mut input = String::new();
std::io::stdin()
.read_to_string(&mut input)
.map_err(|e| IdxError::Io(e.to_string()))?;
let from_stdin: Vec<String> = input
.split(|c: char| c.is_whitespace() || c == ',')
.filter(|s| !s.is_empty())
.map(String::from)
.collect();
if from_stdin.is_empty() {
return Err(IdxError::InvalidInput(
"no symbols read from stdin".to_string(),
));
}
symbols.extend(from_stdin);
} else {
symbols.extend(arg.split(',').map(String::from));
}
}
Ok(symbols)
}
fn cache_bucket(provider: ProviderKind, key: &str) -> String {
format!("{}-{key}", provider.as_str())
}
@ -124,10 +153,10 @@ pub struct StocksCmd {
pub enum StocksSubcommand {
#[command(
about = "Get real-time stock quotes",
after_help = "Examples:\n idx stocks quote BBCA\n idx stocks quote BBCA,BBRI,BMRI\n idx -o json stocks quote BBCA"
after_help = "Examples:\n idx stocks quote BBCA\n idx stocks quote BBCA,BBRI,BMRI\n idx -o json stocks quote BBCA\n echo BBCA BBRI | idx stocks quote -"
)]
Quote {
/// One or more symbols, comma-separated or space-separated.
/// One or more symbols, comma-separated or space-separated; `-` reads them from stdin.
#[arg(required = true)]
symbols: Vec<String>,
},
@ -228,10 +257,10 @@ pub enum StocksSubcommand {
},
#[command(
about = "Compare fundamentals across stocks",
after_help = "Examples:\n idx stocks compare BBCA BBRI BMRI\n idx stocks compare BBCA,BBRI,BMRI\n idx -o json stocks compare BBCA,BBRI"
after_help = "Examples:\n idx stocks compare BBCA BBRI BMRI\n idx stocks compare BBCA,BBRI,BMRI\n idx -o json stocks compare BBCA,BBRI\n idx stocks screen --filter low-pe | jq -r '.[].symbol' | idx stocks compare -"
)]
Compare {
/// One or more symbols, comma-separated or space-separated.
/// One or more symbols, comma-separated or space-separated; `-` reads them from stdin.
#[arg(required = true)]
symbols: Vec<String>,
},
@ -257,8 +286,8 @@ pub fn handle(
StocksSubcommand::Quote { symbols } => {
let quote_bucket = cache_bucket(provider.kind(), "quote");
let mut quotes = Vec::new();
for sym in symbols.iter().flat_map(|s| s.split(',')) {
let resolved = crate::api::resolve_symbol(sym, &config.exchange)?;
for sym in expand_symbols(symbols)? {
let resolved = crate::api::resolve_symbol(&sym, &config.exchange)?;
if !no_cache && let Some(q) = cache.get(&quote_bucket, &resolved)? {
quotes.push(q);
continue;
@ -305,7 +334,7 @@ pub fn handle(
history_provider(provider.kind(), history_mode, verbose)?;
if matches!(history_mode, HistoryProviderKind::Auto)
&& history_source != provider.kind()
&& !matches!(config.output, crate::output::OutputFormat::Json)
&& !config.output.is_json()
{
runtime::info(format!(
"history provider fallback active ({} -> {})",
@ -373,7 +402,7 @@ pub fn handle(
history_provider(provider.kind(), history_mode, verbose)?;
if matches!(history_mode, HistoryProviderKind::Auto)
&& history_source != provider.kind()
&& !matches!(config.output, crate::output::OutputFormat::Json)
&& !config.output.is_json()
{
runtime::info(format!(
"history provider fallback active ({} -> {})",
@ -649,8 +678,8 @@ pub fn handle(
let mut reports: Vec<FundamentalReport> = Vec::new();
let mut failures = Vec::new();
for sym in symbols.iter().flat_map(|s| s.split(',')) {
let resolved = crate::api::resolve_symbol(sym, &config.exchange)?;
for sym in expand_symbols(symbols)? {
let resolved = crate::api::resolve_symbol(&sym, &config.exchange)?;
match fetch_fundamental_analysis_report(
&cache,
provider.market(),

View file

@ -1,4 +1,5 @@
use std::fs;
use std::io::IsTerminal;
use std::path::PathBuf;
use clap::ValueEnum;
@ -7,7 +8,7 @@ use serde::Deserialize;
use crate::cli::Cli;
use crate::error::IdxError;
use crate::output::OutputFormat;
use crate::output::{OutputFormat, OutputSetting};
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
@ -92,7 +93,7 @@ struct FileGeneral {
provider: Option<ProviderKind>,
history_provider: Option<HistoryProviderKind>,
exchange: Option<String>,
output: Option<OutputFormat>,
output: Option<OutputSetting>,
color: Option<bool>,
}
@ -108,7 +109,7 @@ impl Default for IdxConfig {
provider: ProviderKind::Msn,
history_provider: HistoryProviderKind::Auto,
exchange: "JK".to_string(),
output: OutputFormat::Table,
output: OutputSetting::Auto.resolve(stdout_is_terminal()),
no_color: false,
quote_ttl: 300,
fundamental_ttl: 3600,
@ -130,15 +131,12 @@ impl IdxConfig {
cfg.exchange = exchange;
}
if let Ok(output) = std::env::var("IDX_OUTPUT") {
cfg.output = if output.eq_ignore_ascii_case("json") {
OutputFormat::Json
} else if output.eq_ignore_ascii_case("table") {
OutputFormat::Table
} else {
return Err(IdxError::ConfigError(format!(
"invalid IDX_OUTPUT value: '{output}', expected 'json' or 'table'"
)));
};
let setting = OutputSetting::parse(&output).ok_or_else(|| {
IdxError::ConfigError(format!(
"invalid IDX_OUTPUT value: '{output}', expected 'auto', 'table', 'json', or 'jsonl'"
))
})?;
cfg.output = setting.resolve(stdout_is_terminal());
}
if let Ok(no_color) = std::env::var("IDX_NO_COLOR") {
cfg.no_color = no_color == "1" || no_color.eq_ignore_ascii_case("true");
@ -151,9 +149,9 @@ impl IdxConfig {
}
if let Some(output) = cli.output {
cfg.output = output;
cfg.output = output.resolve(stdout_is_terminal());
}
cfg.no_color = cfg.no_color || cli.no_color;
cfg.no_color = cfg.no_color || cli.no_color || !stdout_is_terminal();
Ok(cfg)
}
@ -176,7 +174,7 @@ impl IdxConfig {
cfg.exchange = exchange;
}
if let Some(output) = general.output {
cfg.output = output;
cfg.output = output.resolve(stdout_is_terminal());
}
if let Some(color) = general.color {
cfg.no_color = !color;
@ -216,8 +214,12 @@ fn parse_env_ttl(name: &str) -> Result<Option<u64>, IdxError> {
Ok(Some(parsed))
}
pub fn stdout_is_terminal() -> bool {
std::io::stdout().is_terminal()
}
pub fn default_config_toml() -> String {
"[general]\nprovider = \"msn\"\nhistory_provider = \"auto\"\nexchange = \"JK\"\noutput = \"table\"\ncolor = true\n\n[cache]\nquote_ttl = 300\nfundamental_ttl = 3600\n".to_string()
"[general]\nprovider = \"msn\"\nhistory_provider = \"auto\"\nexchange = \"JK\"\noutput = \"auto\"\ncolor = true\n\n[cache]\nquote_ttl = 300\nfundamental_ttl = 3600\n".to_string()
}
pub fn config_path() -> Result<PathBuf, IdxError> {
@ -292,9 +294,9 @@ fn normalize_config_value(key: &str, value: &str) -> Result<toml::Value, IdxErro
HistoryProviderKind::parse(value)?.as_str().to_string(),
)),
"general.output" => {
if !value.eq_ignore_ascii_case("table") && !value.eq_ignore_ascii_case("json") {
if OutputSetting::parse(value).is_none() {
return Err(IdxError::InvalidInput(format!(
"invalid output format '{value}': expected 'table' or 'json'"
"invalid output format '{value}': expected 'auto', 'table', 'json', or 'jsonl'"
)));
}
Ok(toml::Value::String(value.to_ascii_lowercase()))

View file

@ -76,8 +76,23 @@ impl IdxError {
}
}
/// Exit status by error class, so scripts can branch without parsing
/// stderr. Clap usage errors also exit 2. See docs/DESIGN.md.
pub fn exit_code(&self) -> i32 {
1
match self.code() {
ErrorCode::InvalidInput | ErrorCode::ConfigError => 2,
ErrorCode::SymbolNotFound | ErrorCode::NoMarketData | ErrorCode::CacheMiss => 3,
ErrorCode::Http
| ErrorCode::RateLimited
| ErrorCode::ProviderUnavailable
| ErrorCode::Offline
| ErrorCode::AuthError => 4,
ErrorCode::Unsupported => 5,
ErrorCode::Io
| ErrorCode::ParseError
| ErrorCode::DatabaseError
| ErrorCode::PdfParseError => 1,
}
}
}

View file

@ -44,7 +44,7 @@ use crate::api::default_provider;
use crate::cli::{Cli, Commands};
use crate::config::IdxConfig;
use crate::error::IdxError;
use crate::output::{OutputFormat, emit_error};
use crate::output::{OutputFormat, OutputSetting, emit_error};
fn main() {
if let Err(err) = run() {
@ -62,16 +62,19 @@ fn run() -> Result<(), IdxError> {
return Err(err);
}
};
output::json::configure(config.output, config::stdout_is_terminal());
output::style::set_color(!config.no_color);
match &cli.command {
Commands::Version => match config.output {
OutputFormat::Json => {
Commands::Version => {
if config.output.is_json() {
output::json::print_json(&serde_json::json!({
"version": env!("CARGO_PKG_VERSION")
}))?;
} else {
println!("{}", env!("CARGO_PKG_VERSION"));
}
}
OutputFormat::Table => println!("{}", env!("CARGO_PKG_VERSION")),
},
Commands::Completions { shell } => {
let mut cmd = Cli::command();
let name = cmd.get_name().to_owned();
@ -119,13 +122,15 @@ fn run() -> Result<(), IdxError> {
Ok(())
}
/// Output format for errors raised before the configuration has loaded.
fn bootstrap_output_format(cli: &Cli) -> OutputFormat {
if let Some(output) = cli.output {
return output;
}
match std::env::var("IDX_OUTPUT") {
Ok(value) if value.eq_ignore_ascii_case("json") => OutputFormat::Json,
_ => OutputFormat::Table,
}
let setting = cli
.output
.or_else(|| {
std::env::var("IDX_OUTPUT")
.ok()
.and_then(|value| OutputSetting::parse(&value))
})
.unwrap_or_default();
setting.resolve(config::stdout_is_terminal())
}

View file

@ -1,10 +1,44 @@
use std::sync::atomic::{AtomicU8, Ordering};
use serde::Serialize;
use crate::error::IdxError;
const PRETTY: u8 = 0;
const COMPACT: u8 = 1;
const LINES: u8 = 2;
static STYLE: AtomicU8 = AtomicU8::new(PRETTY);
/// Pretty JSON only for a human at a terminal; compact for pipes and
/// captured output; JSON Lines when requested.
pub fn configure(format: super::OutputFormat, stdout_is_terminal: bool) {
let style = match format {
super::OutputFormat::Jsonl => LINES,
_ if stdout_is_terminal => PRETTY,
_ => COMPACT,
};
STYLE.store(style, Ordering::Relaxed);
}
pub fn print_json<T: Serialize + ?Sized>(value: &T) -> Result<(), IdxError> {
let out =
serde_json::to_string_pretty(value).map_err(|e| IdxError::ParseError(e.to_string()))?;
println!("{out}");
let to_err = |e: serde_json::Error| IdxError::ParseError(e.to_string());
match STYLE.load(Ordering::Relaxed) {
PRETTY => println!("{}", serde_json::to_string_pretty(value).map_err(to_err)?),
COMPACT => println!("{}", serde_json::to_string(value).map_err(to_err)?),
_ => {
// Split an array into one line per item without re-serializing,
// so each line keeps the same key order as `-o json`.
let compact = serde_json::to_string(value).map_err(to_err)?;
match serde_json::from_str::<Vec<&serde_json::value::RawValue>>(&compact) {
Ok(items) => {
for item in items {
println!("{}", item.get());
}
}
Err(_) => println!("{compact}"),
}
}
}
Ok(())
}

View file

@ -1,6 +1,9 @@
pub mod json;
pub mod style;
pub mod table;
use std::io::IsTerminal;
use chrono::NaiveDate;
use clap::ValueEnum;
use serde::{Deserialize, Serialize};
@ -13,12 +16,48 @@ use crate::api::types::{
};
use crate::error::IdxError;
/// Output format requested by the user (flag, `IDX_OUTPUT`, or config file).
#[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq, Serialize, serde::Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum OutputSetting {
// Table on a terminal, JSON when stdout is piped or captured.
#[default]
Auto,
Table,
Json,
// JSON Lines: one array item per line.
Jsonl,
}
impl OutputSetting {
pub fn parse(value: &str) -> Option<Self> {
<Self as ValueEnum>::from_str(value, true).ok()
}
pub fn resolve(self, stdout_is_terminal: bool) -> OutputFormat {
match self {
Self::Auto if stdout_is_terminal => OutputFormat::Table,
Self::Auto | Self::Json => OutputFormat::Json,
Self::Table => OutputFormat::Table,
Self::Jsonl => OutputFormat::Jsonl,
}
}
}
/// Resolved output format used by renderers.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum OutputFormat {
#[default]
Table,
Json,
Jsonl,
}
impl OutputFormat {
/// True for every machine-readable format (JSON and JSON Lines).
pub fn is_json(self) -> bool {
matches!(self, Self::Json | Self::Jsonl)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -56,7 +95,7 @@ pub fn render_quotes(
) -> Result<(), IdxError> {
match format {
OutputFormat::Table => table::print_quotes(quotes, no_color),
OutputFormat::Json => json::print_json(quotes),
OutputFormat::Json | OutputFormat::Jsonl => json::print_json(quotes),
}
}
@ -67,7 +106,7 @@ pub fn render_history(
) -> Result<(), IdxError> {
match format {
OutputFormat::Table => table::print_history(symbol, history),
OutputFormat::Json => json::print_json(history),
OutputFormat::Json | OutputFormat::Jsonl => json::print_json(history),
}
}
@ -78,7 +117,7 @@ pub fn render_technical(
) -> Result<(), IdxError> {
match format {
OutputFormat::Table => table::print_technical(report, no_color),
OutputFormat::Json => json::print_json(report),
OutputFormat::Json | OutputFormat::Jsonl => json::print_json(report),
}
}
@ -90,7 +129,7 @@ pub fn render_growth(
) -> Result<(), IdxError> {
match format {
OutputFormat::Table => table::print_growth(symbol, report, no_color),
OutputFormat::Json => json::print_json(report),
OutputFormat::Json | OutputFormat::Jsonl => json::print_json(report),
}
}
@ -102,7 +141,7 @@ pub fn render_valuation(
) -> Result<(), IdxError> {
match format {
OutputFormat::Table => table::print_valuation(symbol, report, no_color),
OutputFormat::Json => json::print_json(report),
OutputFormat::Json | OutputFormat::Jsonl => json::print_json(report),
}
}
@ -114,7 +153,7 @@ pub fn render_risk(
) -> Result<(), IdxError> {
match format {
OutputFormat::Table => table::print_risk(symbol, report, no_color),
OutputFormat::Json => json::print_json(report),
OutputFormat::Json | OutputFormat::Jsonl => json::print_json(report),
}
}
@ -125,7 +164,7 @@ pub fn render_fundamental(
) -> Result<(), IdxError> {
match format {
OutputFormat::Table => table::print_fundamental(report, no_color),
OutputFormat::Json => json::print_json(report),
OutputFormat::Json | OutputFormat::Jsonl => json::print_json(report),
}
}
@ -136,14 +175,14 @@ pub fn render_compare(
) -> Result<(), IdxError> {
match format {
OutputFormat::Table => table::print_compare(reports, no_color),
OutputFormat::Json => json::print_json(reports),
OutputFormat::Json | OutputFormat::Jsonl => json::print_json(reports),
}
}
pub fn render_profile(profile: &CompanyProfile, format: &OutputFormat) -> Result<(), IdxError> {
match format {
OutputFormat::Table => table::print_profile(profile),
OutputFormat::Json => json::print_json(profile),
OutputFormat::Json | OutputFormat::Jsonl => json::print_json(profile),
}
}
@ -153,35 +192,35 @@ pub fn render_financials(
) -> Result<(), IdxError> {
match format {
OutputFormat::Table => table::print_financials(financials),
OutputFormat::Json => json::print_json(financials),
OutputFormat::Json | OutputFormat::Jsonl => json::print_json(financials),
}
}
pub fn render_earnings(report: &EarningsReport, format: &OutputFormat) -> Result<(), IdxError> {
match format {
OutputFormat::Table => table::print_earnings(report),
OutputFormat::Json => json::print_json(report),
OutputFormat::Json | OutputFormat::Jsonl => json::print_json(report),
}
}
pub fn render_sentiment(data: &SentimentData, format: &OutputFormat) -> Result<(), IdxError> {
match format {
OutputFormat::Table => table::print_sentiment(data),
OutputFormat::Json => json::print_json(data),
OutputFormat::Json | OutputFormat::Jsonl => json::print_json(data),
}
}
pub fn render_insights(data: &InsightData, format: &OutputFormat) -> Result<(), IdxError> {
match format {
OutputFormat::Table => table::print_insights(data),
OutputFormat::Json => json::print_json(data),
OutputFormat::Json | OutputFormat::Jsonl => json::print_json(data),
}
}
pub fn render_news(items: &[NewsItem], format: &OutputFormat) -> Result<(), IdxError> {
match format {
OutputFormat::Table => table::print_news(items),
OutputFormat::Json => json::print_json(items),
OutputFormat::Json | OutputFormat::Jsonl => json::print_json(items),
}
}
@ -192,23 +231,25 @@ pub fn render_screener(
) -> Result<(), IdxError> {
match format {
OutputFormat::Table => table::print_quotes(quotes, no_color),
OutputFormat::Json => json::print_json(quotes),
OutputFormat::Json | OutputFormat::Jsonl => json::print_json(quotes),
}
}
pub fn emit_error(err: &IdxError, format: &OutputFormat) {
match format {
OutputFormat::Table => eprintln!("Error: {err}"),
OutputFormat::Json => {
if !format.is_json() {
eprintln!("Error: {err}");
return;
}
let payload = serde_json::json!({
"error": true,
"code": format!("{:?}", err.code()).to_uppercase(),
"message": err.to_string()
});
eprintln!(
"{}",
serde_json::to_string_pretty(&payload).unwrap_or_else(|_| "{}".to_string())
);
}
}
let pretty = *format == OutputFormat::Json && std::io::stderr().is_terminal();
let rendered = if pretty {
serde_json::to_string_pretty(&payload)
} else {
serde_json::to_string(&payload)
};
eprintln!("{}", rendered.unwrap_or_else(|_| "{}".to_string()));
}

41
src/output/style.rs Normal file
View file

@ -0,0 +1,41 @@
//! Terminal styling that turns itself off when color is disabled or stdout is
//! not a terminal, so piped output never carries ANSI escape codes.
use std::fmt::Display;
use std::sync::atomic::{AtomicBool, Ordering};
use owo_colors::OwoColorize;
static COLOR: AtomicBool = AtomicBool::new(true);
pub fn set_color(enabled: bool) {
COLOR.store(enabled, Ordering::Relaxed);
}
fn paint<T: Display>(value: T, styled: impl FnOnce(&T) -> String) -> String {
if COLOR.load(Ordering::Relaxed) {
styled(&value)
} else {
value.to_string()
}
}
pub trait Paint: Display + Sized {
fn bold(self) -> String {
paint(self, |v| OwoColorize::bold(v).to_string())
}
fn green(self) -> String {
paint(self, |v| OwoColorize::green(v).to_string())
}
fn red(self) -> String {
paint(self, |v| OwoColorize::red(v).to_string())
}
fn yellow(self) -> String {
paint(self, |v| OwoColorize::yellow(v).to_string())
}
fn blue(self) -> String {
paint(self, |v| OwoColorize::blue(v).to_string())
}
}
impl<T: Display> Paint for T {}

View file

@ -1,5 +1,5 @@
use crate::output::style::Paint;
use comfy_table::{Cell, Color, ContentArrangement, Table, presets::UTF8_FULL};
use owo_colors::OwoColorize;
use crate::analysis::fundamental::{FundamentalReport, GrowthReport, RiskReport, ValuationReport};
use crate::analysis::signals::Signal;

View file

@ -73,7 +73,10 @@ fn candidate_is_idx_binary(path: &Path) -> bool {
return false;
}
let Ok(output) = std::process::Command::new(path).arg("version").output() else {
let Ok(output) = std::process::Command::new(path)
.args(["-o", "table", "version"])
.output()
else {
return false;
};
@ -111,6 +114,10 @@ fn bin_with_root(root: &Path) -> Command {
let mut cmd = bin();
cmd.current_dir(env!("CARGO_MANIFEST_DIR"));
// Tests run with piped stdout, where the default (auto) format is JSON.
// Pin table so tests that assert table text keep doing so; `-o` and
// per-test IDX_OUTPUT still override this.
cmd.env("IDX_OUTPUT", "table");
cmd.env("XDG_CONFIG_HOME", &config_home);
cmd.env("XDG_CACHE_HOME", &cache_home);
cmd
@ -1271,6 +1278,7 @@ fn ownership_discover_json_includes_status() {
}"#;
let json_url = spawn_single_response_server("application/json", body.to_string());
let discovered = run_success_json(
test_bin("ownership-discover-json-status")
.env("IDX_CURL_IMPERSONATE_BIN", "curl")
.env("IDX_OWNERSHIP_ANNOUNCEMENT_API_URL", &json_url)
@ -1279,10 +1287,9 @@ fn ownership_discover_json_includes_status() {
"IDX_OWNERSHIP_ANNOUNCEMENT_PAGE_URL",
"http://127.0.0.1/pengumuman",
)
.args(["-o", "json", "ownership", "discover", "--limit", "1"])
.assert()
.success()
.stdout(predicate::str::contains("\"status\": \"supported\""));
.args(["-o", "json", "ownership", "discover", "--limit", "1"]),
);
assert_eq!(discovered[0]["status"], "supported");
}
#[test]

View file

@ -10,8 +10,9 @@
use std::collections::{BTreeMap, BTreeSet};
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::process::{Command, Stdio};
use std::sync::OnceLock;
use serde::Deserialize;
@ -40,6 +41,9 @@ struct Case {
/// Commands run first in the same environment; their output is ignored.
#[serde(default)]
before: Vec<Vec<String>>,
/// Text piped to the command's stdin.
#[serde(default)]
stdin: Option<String>,
}
fn default_formats() -> Vec<String> {
@ -56,6 +60,15 @@ fn work_dir() -> PathBuf {
/// Runs idx with an isolated home under `root` and only the given IDX_* env.
fn idx(root: &Path, args: &[String], env: &BTreeMap<String, String>) -> std::process::Output {
idx_with_stdin(root, args, env, None)
}
fn idx_with_stdin(
root: &Path,
args: &[String],
env: &BTreeMap<String, String>,
stdin: Option<&str>,
) -> std::process::Output {
let mut cmd = Command::new(IDX);
cmd.current_dir(REPO).args(args);
for (key, _) in std::env::vars_os() {
@ -68,8 +81,17 @@ fn idx(root: &Path, args: &[String], env: &BTreeMap<String, String>) -> std::pro
.env("XDG_CACHE_HOME", root.join("cache"))
.env("XDG_DATA_HOME", root.join("data"))
.env("IDX_USE_MOCK_PROVIDER", "1")
.envs(env);
cmd.output().expect("run idx")
.envs(env)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let mut child = cmd.spawn().expect("spawn idx");
let mut pipe = child.stdin.take().expect("stdin pipe");
if let Some(input) = stdin {
pipe.write_all(input.as_bytes()).expect("write stdin");
}
drop(pipe);
child.wait_with_output().expect("run idx")
}
/// Fixture ownership DB: the above-1% XLSX (2026-08-31) plus two KSEI archive
@ -161,7 +183,8 @@ fn cli_output_matches_golden_files() {
idx(&root, before, &case.env);
}
let actual = render(&root, &idx(&root, &args, &case.env));
let output = idx_with_stdin(&root, &args, &case.env, case.stdin.as_deref());
let actual = render(&root, &output);
let path = golden_dir().join(format!("{name}.golden"));
if update {
fs::write(&path, &actual).expect("write golden");

View file

@ -0,0 +1,8 @@
exit: 0
--- stdout
┌─────────┬───────┬──────┬────────┬────────────┬───────────────────────┬────────────┐
│ SYMBOL ┆ PRICE ┆ CHG ┆ CHG% ┆ VOLUME ┆ MKT CAP ┆ 52W RANGE │
╞═════════╪═══════╪══════╪════════╪════════════╪═══════════════════════╪════════════╡
│ BBCA.JK ┆ 9,875 ┆ +117 ┆ +1.20% ┆ 12,300,000 ┆ 1,215,200,000,000,000 ┆ ████████░░ │
└─────────┴───────┴──────┴────────┴────────────┴───────────────────────┴────────────┘
--- stderr

View file

@ -0,0 +1,4 @@
exit: 0
--- stdout
[{"symbol":"BBCA.JK","price":9875,"change":117,"change_pct":1.2,"volume":12300000,"market_cap":1215200000000000,"week52_high":10250,"week52_low":7800,"week52_position":0.8469387755102041,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null},{"symbol":"BBRI.JK","price":9875,"change":117,"change_pct":1.2,"volume":12300000,"market_cap":1215200000000000,"week52_high":10250,"week52_low":7800,"week52_position":0.8469387755102041,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null}]
--- stderr

View file

@ -1,7 +1,4 @@
exit: 0
--- stdout
{
"failed": [],
"removed": 0
}
{"failed":[],"removed":0}
--- stderr

View file

@ -1,10 +1,4 @@
exit: 0
--- stdout
{
"files": 0,
"newest": null,
"oldest": null,
"path": "<ROOT>/cache/idx",
"size_bytes": 0
}
{"files":0,"newest":null,"oldest":null,"path":"<ROOT>/cache/idx","size_bytes":0}
--- stderr

View file

@ -1,9 +1,11 @@
# Golden output cases for tests/golden.rs.
#
# Each case runs once per entry in `formats` (default: table and json) with
# `-o <format>` prepended; `formats = []` runs the args as given. Every run uses
# the mock provider (IDX_USE_MOCK_PROVIDER=1) and an isolated home directory.
# `ownership = true` seeds the fixture DB; `before` runs setup commands first.
# `-o <format>` prepended; `formats = []` runs the args as given, which means
# the auto format: stdout is a pipe here, so auto resolves to compact JSON.
# Every run uses the mock provider (IDX_USE_MOCK_PROVIDER=1) and an isolated
# home directory. `ownership = true` seeds the fixture DB; `before` runs setup
# commands first; `stdin` is piped to the command.
#
# `idx version` is excluded on purpose: its output changes with every release.
@ -62,6 +64,22 @@ name = "config-set"
args = ["config", "set", "general.provider", "yahoo"]
formats = []
[[case]]
name = "config-set-output-jsonl"
args = ["config", "set", "general.output", "jsonl"]
formats = []
[[case]]
name = "config-set-output-invalid"
args = ["config", "set", "general.output", "yaml"]
formats = []
[[case]]
name = "config-output-table-pins-format"
args = ["stocks", "quote", "BBCA"]
formats = []
before = [["config", "set", "general.output", "table"]]
[[case]]
name = "cache-info-empty"
args = ["cache", "info"]
@ -78,10 +96,12 @@ args = ["stocks", "quote", "BBCA"]
[[case]]
name = "stocks-quote-multi"
formats = ["table", "json", "jsonl"]
args = ["stocks", "quote", "BBCA,BBRI"]
[[case]]
name = "stocks-history"
formats = ["table", "json", "jsonl"]
args = ["stocks", "history", "BBCA"]
[[case]]
@ -111,6 +131,7 @@ args = ["stocks", "risk", "BBCA"]
[[case]]
name = "stocks-fundamental"
formats = ["table", "json", "jsonl"]
args = ["stocks", "fundamental", "BBCA"]
[[case]]
@ -169,6 +190,38 @@ args = ["stocks", "screen", "--filter", "high-volume"]
name = "stocks-screen-large-cap"
args = ["stocks", "screen", "--filter", "large-cap"]
[[case]]
name = "auto-format-piped"
args = ["stocks", "quote", "BBCA,BBRI"]
formats = []
[[case]]
name = "auto-format-env-table"
args = ["stocks", "quote", "BBCA"]
formats = []
env = { IDX_OUTPUT = "table" }
[[case]]
name = "stocks-quote-stdin"
args = ["stocks", "quote", "-"]
stdin = "BBCA\nBBRI\n"
[[case]]
name = "stocks-compare-stdin"
args = ["stocks", "compare", "-"]
stdin = "BBCA, BBRI"
[[case]]
name = "error-stdin-empty"
args = ["stocks", "quote", "-"]
stdin = ""
[[case]]
name = "error-invalid-output-env"
args = ["stocks", "quote", "BBCA"]
formats = []
env = { IDX_OUTPUT = "yaml" }
[[case]]
name = "stocks-quote-offline-warm"
args = ["--offline", "stocks", "quote", "BBCA"]
@ -214,6 +267,7 @@ env = { IDX_MOCK_MSN_KEYRATIOS_FIXTURE = "tests/fixtures/msn_keyratios_industry_
[[case]]
name = "error-screen-region"
formats = ["table", "json", "jsonl"]
args = ["stocks", "screen", "--region", "eu"]
[[case]]
@ -298,6 +352,7 @@ ownership = true
[[case]]
name = "ownership-changes"
formats = ["table", "json", "jsonl"]
args = ["ownership", "changes", "--from", "2026-01-30", "--to", "2026-02-27"]
ownership = true

View file

@ -446,11 +446,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -468,11 +468,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -490,11 +490,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -568,11 +568,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -590,11 +590,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -612,11 +612,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -634,11 +634,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -740,11 +740,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -762,11 +762,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -784,11 +784,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -1436,11 +1436,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -1466,11 +1466,11 @@ _idx() {
return 0
;;
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -1496,11 +1496,11 @@ _idx() {
return 0
;;
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -1526,11 +1526,11 @@ _idx() {
return 0
;;
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -1556,11 +1556,11 @@ _idx() {
return 0
;;
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -1578,11 +1578,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -1600,11 +1600,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -1630,11 +1630,11 @@ _idx() {
return 0
;;
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -1916,11 +1916,11 @@ _idx() {
return 0
;;
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -1938,11 +1938,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -1960,11 +1960,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -2052,11 +2052,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -2074,11 +2074,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -2100,11 +2100,11 @@ _idx() {
return 0
;;
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -2126,11 +2126,11 @@ _idx() {
return 0
;;
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -2152,11 +2152,11 @@ _idx() {
return 0
;;
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -2178,11 +2178,11 @@ _idx() {
return 0
;;
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -2200,11 +2200,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -2222,11 +2222,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -2244,11 +2244,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -2270,11 +2270,11 @@ _idx() {
return 0
;;
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -2292,11 +2292,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -2314,11 +2314,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -2586,11 +2586,11 @@ _idx() {
return 0
;;
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -2608,11 +2608,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -2634,11 +2634,11 @@ _idx() {
return 0
;;
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -2656,11 +2656,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -2678,11 +2678,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -2700,11 +2700,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -2734,11 +2734,11 @@ _idx() {
return 0
;;
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -2756,11 +2756,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -2782,11 +2782,11 @@ _idx() {
return 0
;;
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -2804,11 +2804,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)
@ -2826,11 +2826,11 @@ _idx() {
fi
case "${prev}" in
--output)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -W "table json" -- "${cur}"))
COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}"))
return 0
;;
*)

View file

@ -0,0 +1,8 @@
exit: 0
--- stdout
┌─────────┬───────┬──────┬────────┬────────────┬───────────────────────┬────────────┐
│ SYMBOL ┆ PRICE ┆ CHG ┆ CHG% ┆ VOLUME ┆ MKT CAP ┆ 52W RANGE │
╞═════════╪═══════╪══════╪════════╪════════════╪═══════════════════════╪════════════╡
│ BBCA.JK ┆ 9,875 ┆ +117 ┆ +1.20% ┆ 12,300,000 ┆ 1,215,200,000,000,000 ┆ ████████░░ │
└─────────┴───────┴──────┴────────┴────────────┴───────────────────────┴────────────┘
--- stderr

View file

@ -0,0 +1,4 @@
exit: 2
--- stdout
--- stderr
{"code":"INVALIDINPUT","error":true,"message":"invalid input: invalid output format 'yaml': expected 'auto', 'table', 'json', or 'jsonl'"}

View file

@ -0,0 +1,4 @@
exit: 0
--- stdout
ok
--- stderr

View file

@ -1,8 +1,4 @@
exit: 1
exit: 4
--- stdout
--- stderr
{
"code": "PROVIDERUNAVAILABLE",
"error": true,
"message": "provider unavailable"
}
{"code":"PROVIDERUNAVAILABLE","error":true,"message":"provider unavailable"}

View file

@ -1,4 +1,4 @@
exit: 1
exit: 4
--- stdout
--- stderr
Error: provider unavailable

View file

@ -1,8 +1,4 @@
exit: 1
exit: 5
--- stdout
--- stderr
{
"code": "UNSUPPORTED",
"error": true,
"message": "unsupported: --fetch-bing import is not implemented yet"
}
{"code":"UNSUPPORTED","error":true,"message":"unsupported: --fetch-bing import is not implemented yet"}

View file

@ -1,4 +1,4 @@
exit: 1
exit: 5
--- stdout
--- stderr
Error: unsupported: --fetch-bing import is not implemented yet

View file

@ -1,8 +1,4 @@
exit: 1
--- stdout
--- stderr
{
"code": "PARSEERROR",
"error": true,
"message": "parse error: unsupported: company fundamentals unavailable from MSN; industry fallback is disabled"
}
{"code":"PARSEERROR","error":true,"message":"parse error: unsupported: company fundamentals unavailable from MSN; industry fallback is disabled"}

View file

@ -0,0 +1,4 @@
exit: 2
--- stdout
--- stderr
{"code":"CONFIGERROR","error":true,"message":"config error: invalid IDX_OUTPUT value: 'yaml', expected 'auto', 'table', 'json', or 'jsonl'"}

View file

@ -1,8 +1,4 @@
exit: 1
exit: 2
--- stdout
--- stderr
{
"code": "CONFIGERROR",
"error": true,
"message": "config error: invalid provider 'bogus' (expected yahoo or msn)"
}
{"code":"CONFIGERROR","error":true,"message":"config error: invalid provider 'bogus' (expected yahoo or msn)"}

View file

@ -1,4 +1,4 @@
exit: 1
exit: 2
--- stdout
--- stderr
Error: config error: invalid provider 'bogus' (expected yahoo or msn)

View file

@ -1,8 +1,4 @@
exit: 1
exit: 3
--- stdout
--- stderr
{
"code": "CACHEMISS",
"error": true,
"message": "cache miss: msn-quote/BBCA.JK"
}
{"code":"CACHEMISS","error":true,"message":"cache miss: msn-quote/BBCA.JK"}

View file

@ -1,4 +1,4 @@
exit: 1
exit: 3
--- stdout
--- stderr
Error: cache miss: msn-quote/BBCA.JK

View file

@ -1,8 +1,4 @@
exit: 1
exit: 2
--- stdout
--- stderr
{
"code": "INVALIDINPUT",
"error": true,
"message": "invalid input: cannot combine --offline with --no-cache"
}
{"code":"INVALIDINPUT","error":true,"message":"invalid input: cannot combine --offline with --no-cache"}

View file

@ -1,4 +1,4 @@
exit: 1
exit: 2
--- stdout
--- stderr
Error: invalid input: cannot combine --offline with --no-cache

View file

@ -1,8 +1,4 @@
exit: 1
exit: 4
--- stdout
--- stderr
{
"code": "PROVIDERUNAVAILABLE",
"error": true,
"message": "provider unavailable"
}
{"code":"PROVIDERUNAVAILABLE","error":true,"message":"provider unavailable"}

View file

@ -1,4 +1,4 @@
exit: 1
exit: 4
--- stdout
--- stderr
Error: provider unavailable

View file

@ -1,8 +1,4 @@
exit: 1
exit: 2
--- stdout
--- stderr
{
"code": "INVALIDINPUT",
"error": true,
"message": "invalid input: invalid screener region 'eu' (expected one of: id, us, sg, hk, jp)"
}
{"code":"INVALIDINPUT","error":true,"message":"invalid input: invalid screener region 'eu' (expected one of: id, us, sg, hk, jp)"}

View file

@ -0,0 +1,4 @@
exit: 2
--- stdout
--- stderr
{"code":"INVALIDINPUT","error":true,"message":"invalid input: invalid screener region 'eu' (expected one of: id, us, sg, hk, jp)"}

View file

@ -1,4 +1,4 @@
exit: 1
exit: 2
--- stdout
--- stderr
Error: invalid input: invalid screener region 'eu' (expected one of: id, us, sg, hk, jp)

View file

@ -0,0 +1,4 @@
exit: 2
--- stdout
--- stderr
{"code":"INVALIDINPUT","error":true,"message":"invalid input: no symbols read from stdin"}

View file

@ -0,0 +1,4 @@
exit: 2
--- stdout
--- stderr
Error: invalid input: no symbols read from stdin

View file

@ -1,8 +1,4 @@
exit: 1
exit: 5
--- stdout
--- stderr
{
"code": "UNSUPPORTED",
"error": true,
"message": "unsupported: BBCA.JK: command requires --provider msn"
}
{"code":"UNSUPPORTED","error":true,"message":"unsupported: BBCA.JK: command requires --provider msn"}

View file

@ -1,4 +1,4 @@
exit: 1
exit: 5
--- stdout
--- stderr
Error: unsupported: BBCA.JK: command requires --provider msn

View file

@ -10,7 +10,7 @@ Commands:
help Print this message or the help of the given subcommand(s)
Options:
-o, --output <OUTPUT> Output format [possible values: table, json]
-o, --output <OUTPUT> Output format [default: auto = table on a terminal, json when piped] [possible values: auto, table, json, jsonl]
--no-color Disable colored output
-q, --quiet Suppress non-essential output
-v, --verbose... Increase verbosity (-v, -vv)

View file

@ -12,7 +12,7 @@ Commands:
help Print this message or the help of the given subcommand(s)
Options:
-o, --output <OUTPUT> Output format [possible values: table, json]
-o, --output <OUTPUT> Output format [default: auto = table on a terminal, json when piped] [possible values: auto, table, json, jsonl]
--no-color Disable colored output
-q, --quiet Suppress non-essential output
-v, --verbose... Increase verbosity (-v, -vv)

View file

@ -27,9 +27,9 @@ Commands:
Options:
-o, --output <OUTPUT>
Output format
Output format [default: auto = table on a terminal, json when piped]
[possible values: table, json]
[possible values: auto, table, json, jsonl]
--no-color
Disable colored output

View file

@ -16,9 +16,9 @@ Commands:
Options:
-o, --output <OUTPUT>
Output format
Output format [default: auto = table on a terminal, json when piped]
[possible values: table, json]
[possible values: auto, table, json, jsonl]
--no-color
Disable colored output

View file

@ -23,7 +23,7 @@ Commands:
help Print this message or the help of the given subcommand(s)
Options:
-o, --output <OUTPUT> Output format [possible values: table, json]
-o, --output <OUTPUT> Output format [default: auto = table on a terminal, json when piped] [possible values: auto, table, json, jsonl]
--no-color Disable colored output
-q, --quiet Suppress non-essential output
-v, --verbose... Increase verbosity (-v, -vv)

View file

@ -1,109 +1,4 @@
exit: 0
--- stdout
[
{
"ticker_code": "AADI",
"entity_name": "KSEI AGGREGATE LOCAL ID",
"change_type": "decreased",
"old_bps": 2400,
"new_bps": 2334,
"delta_bps": -66
},
{
"ticker_code": "AADI",
"entity_name": "KSEI AGGREGATE LOCAL IS",
"change_type": "increased",
"old_bps": 75,
"new_bps": 129,
"delta_bps": 54
},
{
"ticker_code": "AADI",
"entity_name": "KSEI AGGREGATE LOCAL MF",
"change_type": "increased",
"old_bps": 117,
"new_bps": 152,
"delta_bps": 35
},
{
"ticker_code": "AADI",
"entity_name": "KSEI AGGREGATE FOREIGN MF",
"change_type": "increased",
"old_bps": 307,
"new_bps": 337,
"delta_bps": 30
},
{
"ticker_code": "AADI",
"entity_name": "KSEI AGGREGATE FOREIGN OT",
"change_type": "decreased",
"old_bps": 91,
"new_bps": 64,
"delta_bps": -27
},
{
"ticker_code": "AADI",
"entity_name": "KSEI AGGREGATE FOREIGN PF",
"change_type": "decreased",
"old_bps": 76,
"new_bps": 56,
"delta_bps": -20
},
{
"ticker_code": "AADI",
"entity_name": "KSEI AGGREGATE FOREIGN CP",
"change_type": "increased",
"old_bps": 72,
"new_bps": 86,
"delta_bps": 14
},
{
"ticker_code": "AADI",
"entity_name": "KSEI AGGREGATE LOCAL CP",
"change_type": "decreased",
"old_bps": 6481,
"new_bps": 6467,
"delta_bps": -14
},
{
"ticker_code": "AADI",
"entity_name": "KSEI AGGREGATE LOCAL SC",
"change_type": "decreased",
"old_bps": 36,
"new_bps": 29,
"delta_bps": -7
},
{
"ticker_code": "AADI",
"entity_name": "KSEI AGGREGATE FOREIGN IB",
"change_type": "decreased",
"old_bps": 267,
"new_bps": 263,
"delta_bps": -4
},
{
"ticker_code": "AADI",
"entity_name": "KSEI AGGREGATE FOREIGN SC",
"change_type": "increased",
"old_bps": 51,
"new_bps": 55,
"delta_bps": 4
},
{
"ticker_code": "AADI",
"entity_name": "KSEI AGGREGATE LOCAL PF",
"change_type": "increased",
"old_bps": 17,
"new_bps": 20,
"delta_bps": 3
},
{
"ticker_code": "AADI",
"entity_name": "KSEI AGGREGATE FOREIGN ID",
"change_type": "decreased",
"old_bps": 3,
"new_bps": 2,
"delta_bps": -1
}
]
[{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL ID","change_type":"decreased","old_bps":2400,"new_bps":2334,"delta_bps":-66},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL IS","change_type":"increased","old_bps":75,"new_bps":129,"delta_bps":54},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL MF","change_type":"increased","old_bps":117,"new_bps":152,"delta_bps":35},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN MF","change_type":"increased","old_bps":307,"new_bps":337,"delta_bps":30},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN OT","change_type":"decreased","old_bps":91,"new_bps":64,"delta_bps":-27},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN PF","change_type":"decreased","old_bps":76,"new_bps":56,"delta_bps":-20},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN CP","change_type":"increased","old_bps":72,"new_bps":86,"delta_bps":14},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL CP","change_type":"decreased","old_bps":6481,"new_bps":6467,"delta_bps":-14},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL SC","change_type":"decreased","old_bps":36,"new_bps":29,"delta_bps":-7},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN IB","change_type":"decreased","old_bps":267,"new_bps":263,"delta_bps":-4},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN SC","change_type":"increased","old_bps":51,"new_bps":55,"delta_bps":4},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL PF","change_type":"increased","old_bps":17,"new_bps":20,"delta_bps":3},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN ID","change_type":"decreased","old_bps":3,"new_bps":2,"delta_bps":-1}]
--- stderr

View file

@ -0,0 +1,16 @@
exit: 0
--- stdout
{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL ID","change_type":"decreased","old_bps":2400,"new_bps":2334,"delta_bps":-66}
{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL IS","change_type":"increased","old_bps":75,"new_bps":129,"delta_bps":54}
{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL MF","change_type":"increased","old_bps":117,"new_bps":152,"delta_bps":35}
{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN MF","change_type":"increased","old_bps":307,"new_bps":337,"delta_bps":30}
{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN OT","change_type":"decreased","old_bps":91,"new_bps":64,"delta_bps":-27}
{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN PF","change_type":"decreased","old_bps":76,"new_bps":56,"delta_bps":-20}
{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN CP","change_type":"increased","old_bps":72,"new_bps":86,"delta_bps":14}
{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL CP","change_type":"decreased","old_bps":6481,"new_bps":6467,"delta_bps":-14}
{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL SC","change_type":"decreased","old_bps":36,"new_bps":29,"delta_bps":-7}
{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN IB","change_type":"decreased","old_bps":267,"new_bps":263,"delta_bps":-4}
{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN SC","change_type":"increased","old_bps":51,"new_bps":55,"delta_bps":4}
{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL PF","change_type":"increased","old_bps":17,"new_bps":20,"delta_bps":3}
{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN ID","change_type":"decreased","old_bps":3,"new_bps":2,"delta_bps":-1}
--- stderr

View file

@ -1,32 +1,32 @@
exit: 0
--- stdout
┌────────┬───────────────────────────┬─────────────────────┬────────┬────────┬────────┐
┌────────┬───────────────────────────┬───────────┬────────┬────────┬────────┐
│ TICKER ┆ ENTITY ┆ CHANGE ┆ OLD% ┆ NEW% ┆ DELTA% │
╞════════╪═══════════════════════════╪═════════════════════╪════════╪════════╪════════╡
│ AADI ┆ KSEI AGGREGATE LOCAL ID ┆ DECREASED ┆ 24.00% ┆ 23.34% ┆ -0.66% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE LOCAL IS ┆ INCREASED ┆ 0.75% ┆ 1.29% ┆ +0.54% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE LOCAL MF ┆ INCREASED ┆ 1.17% ┆ 1.52% ┆ +0.35% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE FOREIGN MF ┆ INCREASED ┆ 3.07% ┆ 3.37% ┆ +0.30% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE FOREIGN OT ┆ DECREASED ┆ 0.91% ┆ 0.64% ┆ -0.27% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE FOREIGN PF ┆ DECREASED ┆ 0.76% ┆ 0.56% ┆ -0.20% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE FOREIGN CP ┆ INCREASED ┆ 0.72% ┆ 0.86% ┆ +0.14% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE LOCAL CP ┆ DECREASED ┆ 64.81% ┆ 64.67% ┆ -0.14% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE LOCAL SC ┆ DECREASED ┆ 0.36% ┆ 0.29% ┆ -0.07% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE FOREIGN IB ┆ DECREASED ┆ 2.67% ┆ 2.63% ┆ -0.04% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE FOREIGN SC ┆ INCREASED ┆ 0.51% ┆ 0.55% ┆ +0.04% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE LOCAL PF ┆ INCREASED ┆ 0.17% ┆ 0.20% ┆ +0.03% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE FOREIGN ID ┆ DECREASED ┆ 0.03% ┆ 0.02% ┆ -0.01% │
└────────┴───────────────────────────┴─────────────────────┴────────┴────────┴────────┘
╞════════╪═══════════════════════════╪═══════════╪════════╪════════╪════════╡
│ AADI ┆ KSEI AGGREGATE LOCAL ID ┆ DECREASED ┆ 24.00% ┆ 23.34% ┆ -0.66% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE LOCAL IS ┆ INCREASED ┆ 0.75% ┆ 1.29% ┆ +0.54% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE LOCAL MF ┆ INCREASED ┆ 1.17% ┆ 1.52% ┆ +0.35% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE FOREIGN MF ┆ INCREASED ┆ 3.07% ┆ 3.37% ┆ +0.30% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE FOREIGN OT ┆ DECREASED ┆ 0.91% ┆ 0.64% ┆ -0.27% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE FOREIGN PF ┆ DECREASED ┆ 0.76% ┆ 0.56% ┆ -0.20% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE FOREIGN CP ┆ INCREASED ┆ 0.72% ┆ 0.86% ┆ +0.14% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE LOCAL CP ┆ DECREASED ┆ 64.81% ┆ 64.67% ┆ -0.14% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE LOCAL SC ┆ DECREASED ┆ 0.36% ┆ 0.29% ┆ -0.07% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE FOREIGN IB ┆ DECREASED ┆ 2.67% ┆ 2.63% ┆ -0.04% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE FOREIGN SC ┆ INCREASED ┆ 0.51% ┆ 0.55% ┆ +0.04% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE LOCAL PF ┆ INCREASED ┆ 0.17% ┆ 0.20% ┆ +0.03% │
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ AADI ┆ KSEI AGGREGATE FOREIGN ID ┆ DECREASED ┆ 0.03% ┆ 0.02% ┆ -0.01% │
└────────┴───────────────────────────┴───────────┴────────┴────────┴────────┘
--- stderr

View file

@ -1,45 +1,4 @@
exit: 0
--- stdout
[
[
"AALI",
{
"top1_bps": 7968,
"top3_bps": 7968,
"hhi": 6348,
"free_float_bps": 2032,
"holder_count": 1
}
],
[
"BBCA",
{
"top1_bps": 5494,
"top3_bps": 5716,
"hhi": 3021,
"free_float_bps": 4182,
"holder_count": 4
}
],
[
"AADI",
{
"top1_bps": 4110,
"top3_bps": 6230,
"hhi": 2009,
"free_float_bps": 2030,
"holder_count": 10
}
],
[
"ABBA",
{
"top1_bps": 283,
"top3_bps": 418,
"hhi": 9,
"free_float_bps": 9582,
"holder_count": 2
}
]
]
[["AALI",{"top1_bps":7968,"top3_bps":7968,"hhi":6348,"free_float_bps":2032,"holder_count":1}],["BBCA",{"top1_bps":5494,"top3_bps":5716,"hhi":3021,"free_float_bps":4182,"holder_count":4}],["AADI",{"top1_bps":4110,"top3_bps":6230,"hhi":2009,"free_float_bps":2030,"holder_count":10}],["ABBA",{"top1_bps":283,"top3_bps":418,"hhi":9,"free_float_bps":9582,"holder_count":2}]]
--- stderr

View file

@ -1,175 +1,4 @@
exit: 0
--- stdout
[
{
"entity": {
"id": 11,
"canonical_name": "ASTRA INTERNATIONAL",
"entity_type": null,
"country": null
},
"ticker_count": 1,
"total_bps": 7968
},
{
"entity": {
"id": 14,
"canonical_name": "DWIMURIA INVESTAMA ANDALAN",
"entity_type": null,
"country": null
},
"ticker_count": 1,
"total_bps": 5494
},
{
"entity": {
"id": 4,
"canonical_name": "ADARO STRATEGIC INVESTMENTS",
"entity_type": null,
"country": null
},
"ticker_count": 1,
"total_bps": 4110
},
{
"entity": {
"id": 5,
"canonical_name": "ALAMTRI RESOURCES INDONESIA TBK PT",
"entity_type": null,
"country": null
},
"ticker_count": 1,
"total_bps": 1537
},
{
"entity": {
"id": 7,
"canonical_name": "GARIBALDI THOHIR",
"entity_type": null,
"country": null
},
"ticker_count": 1,
"total_bps": 583
},
{
"entity": {
"id": 2,
"canonical_name": "SARATOGA INVESTAMA SEDAYA",
"entity_type": null,
"country": null
},
"ticker_count": 1,
"total_bps": 438
},
{
"entity": {
"id": 9,
"canonical_name": "EDWIN SOERYADJAYA",
"entity_type": null,
"country": null
},
"ticker_count": 1,
"total_bps": 352
},
{
"entity": {
"id": 8,
"canonical_name": "IR. T. PERMADI RACHMAT",
"entity_type": null,
"country": null
},
"ticker_count": 1,
"total_bps": 348
},
{
"entity": {
"id": 13,
"canonical_name": "YAYASAN ABDI BANGSA",
"entity_type": null,
"country": null
},
"ticker_count": 1,
"total_bps": 283
},
{
"entity": {
"id": 10,
"canonical_name": "SANDIAGA SALAHUDDIN UNO",
"entity_type": null,
"country": null
},
"ticker_count": 1,
"total_bps": 204
},
{
"entity": {
"id": 1,
"canonical_name": "PERSADA CAPITAL INVESTAMA",
"entity_type": null,
"country": null
},
"ticker_count": 1,
"total_bps": 154
},
{
"entity": {
"id": 12,
"canonical_name": "YAYASAN BINA SEJAHTERA WARGA BULOG",
"entity_type": null,
"country": null
},
"ticker_count": 1,
"total_bps": 135
},
{
"entity": {
"id": 6,
"canonical_name": "FTAG INVESTMENT BANK",
"entity_type": null,
"country": null
},
"ticker_count": 1,
"total_bps": 129
},
{
"entity": {
"id": 17,
"canonical_name": "ANTHONI SALIM",
"entity_type": null,
"country": null
},
"ticker_count": 1,
"total_bps": 115
},
{
"entity": {
"id": 3,
"canonical_name": "TRINUGRAHA THOHIR",
"entity_type": null,
"country": null
},
"ticker_count": 1,
"total_bps": 115
},
{
"entity": {
"id": 16,
"canonical_name": "TRICIPTA MANDHALA GUMILANG",
"entity_type": null,
"country": null
},
"ticker_count": 1,
"total_bps": 107
},
{
"entity": {
"id": 15,
"canonical_name": "CATURGUWIRATNA SUMAPALA",
"entity_type": null,
"country": null
},
"ticker_count": 1,
"total_bps": 102
}
]
[{"entity":{"id":11,"canonical_name":"ASTRA INTERNATIONAL","entity_type":null,"country":null},"ticker_count":1,"total_bps":7968},{"entity":{"id":14,"canonical_name":"DWIMURIA INVESTAMA ANDALAN","entity_type":null,"country":null},"ticker_count":1,"total_bps":5494},{"entity":{"id":4,"canonical_name":"ADARO STRATEGIC INVESTMENTS","entity_type":null,"country":null},"ticker_count":1,"total_bps":4110},{"entity":{"id":5,"canonical_name":"ALAMTRI RESOURCES INDONESIA TBK PT","entity_type":null,"country":null},"ticker_count":1,"total_bps":1537},{"entity":{"id":7,"canonical_name":"GARIBALDI THOHIR","entity_type":null,"country":null},"ticker_count":1,"total_bps":583},{"entity":{"id":2,"canonical_name":"SARATOGA INVESTAMA SEDAYA","entity_type":null,"country":null},"ticker_count":1,"total_bps":438},{"entity":{"id":9,"canonical_name":"EDWIN SOERYADJAYA","entity_type":null,"country":null},"ticker_count":1,"total_bps":352},{"entity":{"id":8,"canonical_name":"IR. T. PERMADI RACHMAT","entity_type":null,"country":null},"ticker_count":1,"total_bps":348},{"entity":{"id":13,"canonical_name":"YAYASAN ABDI BANGSA","entity_type":null,"country":null},"ticker_count":1,"total_bps":283},{"entity":{"id":10,"canonical_name":"SANDIAGA SALAHUDDIN UNO","entity_type":null,"country":null},"ticker_count":1,"total_bps":204},{"entity":{"id":1,"canonical_name":"PERSADA CAPITAL INVESTAMA","entity_type":null,"country":null},"ticker_count":1,"total_bps":154},{"entity":{"id":12,"canonical_name":"YAYASAN BINA SEJAHTERA WARGA BULOG","entity_type":null,"country":null},"ticker_count":1,"total_bps":135},{"entity":{"id":6,"canonical_name":"FTAG INVESTMENT BANK","entity_type":null,"country":null},"ticker_count":1,"total_bps":129},{"entity":{"id":17,"canonical_name":"ANTHONI SALIM","entity_type":null,"country":null},"ticker_count":1,"total_bps":115},{"entity":{"id":3,"canonical_name":"TRINUGRAHA THOHIR","entity_type":null,"country":null},"ticker_count":1,"total_bps":115},{"entity":{"id":16,"canonical_name":"TRICIPTA MANDHALA GUMILANG","entity_type":null,"country":null},"ticker_count":1,"total_bps":107},{"entity":{"id":15,"canonical_name":"CATURGUWIRATNA SUMAPALA","entity_type":null,"country":null},"ticker_count":1,"total_bps":102}]
--- stderr

View file

@ -1,26 +1,4 @@
exit: 0
--- stdout
{
"entity": {
"id": 17,
"canonical_name": "ANTHONI SALIM",
"entity_type": null,
"country": null
},
"ticker_count": 1,
"holdings": [
{
"ticker": {
"id": 4,
"code": "BBCA",
"name": "BANK CENTRAL ASIA Tbk",
"sector": null
},
"source": "ksei",
"shares": 1416306835,
"percentage_bps": 115,
"report_date": "2026-08-31"
}
]
}
{"entity":{"id":17,"canonical_name":"ANTHONI SALIM","entity_type":null,"country":null},"ticker_count":1,"holdings":[{"ticker":{"id":4,"code":"BBCA","name":"BANK CENTRAL ASIA Tbk","sector":null},"source":"ksei","shares":1416306835,"percentage_bps":115,"report_date":"2026-08-31"}]}
--- stderr

View file

@ -1,10 +1,10 @@
exit: 0
--- stdout
Entity: ANTHONI SALIM
Entity: ANTHONI SALIM
┌────────┬────────┬───────────────┬───────┬─────────────┐
│ TICKER ┆ SOURCE ┆ SHARES ┆ % ┆ REPORT_DATE │
╞════════╪════════╪═══════════════╪═══════╪═════════════╡
│ BBCA ┆ KSEI ┆ 1,416,306,835 ┆ 1.15% ┆ 2026-08-31 │
└────────┴────────┴───────────────┴───────┴─────────────┘
Total tickers: 1
Total tickers: 1
--- stderr

View file

@ -1,58 +1,4 @@
exit: 0
--- stdout
{
"nodes": [
{
"id": "entity:14",
"label": "DWIMURIA INVESTAMA ANDALAN",
"node_type": "entity"
},
{
"id": "entity:15",
"label": "CATURGUWIRATNA SUMAPALA",
"node_type": "entity"
},
{
"id": "entity:16",
"label": "TRICIPTA MANDHALA GUMILANG",
"node_type": "entity"
},
{
"id": "entity:17",
"label": "ANTHONI SALIM",
"node_type": "entity"
},
{
"id": "ticker:BBCA",
"label": "BBCA",
"node_type": "ticker"
}
],
"edges": [
{
"from": "entity:17",
"to": "ticker:BBCA",
"percentage_bps": 115,
"source": "ksei"
},
{
"from": "entity:15",
"to": "ticker:BBCA",
"percentage_bps": 102,
"source": "ksei"
},
{
"from": "entity:14",
"to": "ticker:BBCA",
"percentage_bps": 5494,
"source": "ksei"
},
{
"from": "entity:16",
"to": "ticker:BBCA",
"percentage_bps": 107,
"source": "ksei"
}
]
}
{"nodes":[{"id":"entity:14","label":"DWIMURIA INVESTAMA ANDALAN","node_type":"entity"},{"id":"entity:15","label":"CATURGUWIRATNA SUMAPALA","node_type":"entity"},{"id":"entity:16","label":"TRICIPTA MANDHALA GUMILANG","node_type":"entity"},{"id":"entity:17","label":"ANTHONI SALIM","node_type":"entity"},{"id":"ticker:BBCA","label":"BBCA","node_type":"ticker"}],"edges":[{"from":"entity:17","to":"ticker:BBCA","percentage_bps":115,"source":"ksei"},{"from":"entity:15","to":"ticker:BBCA","percentage_bps":102,"source":"ksei"},{"from":"entity:14","to":"ticker:BBCA","percentage_bps":5494,"source":"ksei"},{"from":"entity:16","to":"ticker:BBCA","percentage_bps":107,"source":"ksei"}]}
--- stderr

View file

@ -1,58 +1,4 @@
exit: 0
--- stdout
{
"nodes": [
{
"id": "entity:14",
"label": "DWIMURIA INVESTAMA ANDALAN",
"node_type": "entity"
},
{
"id": "entity:15",
"label": "CATURGUWIRATNA SUMAPALA",
"node_type": "entity"
},
{
"id": "entity:16",
"label": "TRICIPTA MANDHALA GUMILANG",
"node_type": "entity"
},
{
"id": "entity:17",
"label": "ANTHONI SALIM",
"node_type": "entity"
},
{
"id": "ticker:BBCA",
"label": "BBCA",
"node_type": "ticker"
}
],
"edges": [
{
"from": "entity:17",
"to": "ticker:BBCA",
"percentage_bps": 115,
"source": "ksei"
},
{
"from": "entity:15",
"to": "ticker:BBCA",
"percentage_bps": 102,
"source": "ksei"
},
{
"from": "entity:14",
"to": "ticker:BBCA",
"percentage_bps": 5494,
"source": "ksei"
},
{
"from": "entity:16",
"to": "ticker:BBCA",
"percentage_bps": 107,
"source": "ksei"
}
]
}
{"nodes":[{"id":"entity:14","label":"DWIMURIA INVESTAMA ANDALAN","node_type":"entity"},{"id":"entity:15","label":"CATURGUWIRATNA SUMAPALA","node_type":"entity"},{"id":"entity:16","label":"TRICIPTA MANDHALA GUMILANG","node_type":"entity"},{"id":"entity:17","label":"ANTHONI SALIM","node_type":"entity"},{"id":"ticker:BBCA","label":"BBCA","node_type":"ticker"}],"edges":[{"from":"entity:17","to":"ticker:BBCA","percentage_bps":115,"source":"ksei"},{"from":"entity:15","to":"ticker:BBCA","percentage_bps":102,"source":"ksei"},{"from":"entity:14","to":"ticker:BBCA","percentage_bps":5494,"source":"ksei"},{"from":"entity:16","to":"ticker:BBCA","percentage_bps":107,"source":"ksei"}]}
--- stderr

View file

@ -1,11 +1,4 @@
exit: 0
--- stdout
{
"action": "imported",
"inserted_rows": 18,
"ticker_count": 1,
"as_of_date": "2026-02-27",
"sha256": "7b8da1d820f67062410c48988bb6c70fec2088c24961d481d1c0fce8b9372547",
"source_url": null
}
{"action":"imported","inserted_rows":18,"ticker_count":1,"as_of_date":"2026-02-27","sha256":"7b8da1d820f67062410c48988bb6c70fec2088c24961d481d1c0fce8b9372547","source_url":null}
--- stderr

View file

@ -1,11 +1,4 @@
exit: 0
--- stdout
{
"action": "imported",
"inserted_rows": 17,
"ticker_count": 4,
"as_of_date": "2026-08-31",
"sha256": "7c1a4c209b8ce5bde9112436a0c64d66b042915995b584d2bbf7ca7b15ce81e3",
"source_url": null
}
{"action":"imported","inserted_rows":17,"ticker_count":4,"as_of_date":"2026-08-31","sha256":"7c1a4c209b8ce5bde9112436a0c64d66b042915995b584d2bbf7ca7b15ce81e3","source_url":null}
--- stderr

View file

@ -1,29 +1,4 @@
exit: 0
--- stdout
[
{
"id": 1,
"source_url": null,
"sha256": "7c1a4c209b8ce5bde9112436a0c64d66b042915995b584d2bbf7ca7b15ce81e3",
"as_of_date": "2026-08-31",
"row_count": 17,
"imported_at": 0
},
{
"id": 3,
"source_url": null,
"sha256": "7b8da1d820f67062410c48988bb6c70fec2088c24961d481d1c0fce8b9372547",
"as_of_date": "2026-02-27",
"row_count": 18,
"imported_at": 0
},
{
"id": 2,
"source_url": null,
"sha256": "efb3d8fb19c9e0d803dc1b1734c8389356605d0f78d73db6729fb76d3d75753d",
"as_of_date": "2026-01-30",
"row_count": 18,
"imported_at": 0
}
]
[{"id":1,"source_url":null,"sha256":"7c1a4c209b8ce5bde9112436a0c64d66b042915995b584d2bbf7ca7b15ce81e3","as_of_date":"2026-08-31","row_count":17,"imported_at":0},{"id":3,"source_url":null,"sha256":"7b8da1d820f67062410c48988bb6c70fec2088c24961d481d1c0fce8b9372547","as_of_date":"2026-02-27","row_count":18,"imported_at":0},{"id":2,"source_url":null,"sha256":"efb3d8fb19c9e0d803dc1b1734c8389356605d0f78d73db6729fb76d3d75753d","as_of_date":"2026-01-30","row_count":18,"imported_at":0}]
--- stderr

View file

@ -1,8 +1,4 @@
exit: 0
--- stdout
{
"action": "mapped",
"alias": "SALIM ANTHONI",
"entity": "ANTHONI SALIM"
}
{"action":"mapped","alias":"SALIM ANTHONI","entity":"ANTHONI SALIM"}
--- stderr

View file

@ -1,11 +1,4 @@
exit: 0
--- stdout
{
"action": "merged",
"alias_updates": 1,
"bing_holdings_updates": 0,
"keep": 1,
"ksei_holdings_updates": 1,
"merge": 2
}
{"action":"merged","alias_updates":1,"bing_holdings_updates":0,"keep":1,"ksei_holdings_updates":1,"merge":2}
--- stderr

View file

@ -1,11 +1,4 @@
exit: 0
--- stdout
[
{
"id": 17,
"canonical_name": "ANTHONI SALIM",
"entity_type": null,
"country": null
}
]
[{"id":17,"canonical_name":"ANTHONI SALIM","entity_type":null,"country":null}]
--- stderr

View file

@ -1,8 +1,4 @@
exit: 1
exit: 3
--- stdout
--- stderr
{
"code": "SYMBOLNOTFOUND",
"error": true,
"message": "symbol not found: BBCA"
}
{"code":"SYMBOLNOTFOUND","error":true,"message":"symbol not found: BBCA"}

View file

@ -1,4 +1,4 @@
exit: 1
exit: 3
--- stdout
--- stderr
Error: symbol not found: BBCA

View file

@ -1,67 +1,4 @@
exit: 0
--- stdout
{
"ticker": {
"id": 4,
"code": "BBCA",
"name": "BANK CENTRAL ASIA Tbk",
"sector": null
},
"ksei_as_of": "2026-08-31",
"bing_as_of": null,
"holders": [
{
"rank": 1,
"source": "ksei",
"name": "DWIMURIA INVESTAMA ANDALAN",
"entity_id": 14,
"investor_type": "CORPORATE",
"locality": "Local",
"shares": 67729950000,
"percentage_bps": 5494,
"signal": null
},
{
"rank": 2,
"source": "ksei",
"name": "ANTHONI SALIM",
"entity_id": 17,
"investor_type": "INDIVIDUAL",
"locality": "Local",
"shares": 1416306835,
"percentage_bps": 115,
"signal": null
},
{
"rank": 3,
"source": "ksei",
"name": "TRICIPTA MANDHALA GUMILANG",
"entity_id": 16,
"investor_type": "CORPORATE",
"locality": "Local",
"shares": 1313250000,
"percentage_bps": 107,
"signal": null
},
{
"rank": 4,
"source": "ksei",
"name": "CATURGUWIRATNA SUMAPALA",
"entity_id": 15,
"investor_type": "CORPORATE",
"locality": "Local",
"shares": 1261750000,
"percentage_bps": 102,
"signal": null
}
],
"concentration": {
"top1_bps": 5494,
"top3_bps": 5716,
"hhi": 3021,
"free_float_bps": 4182,
"holder_count": 4
},
"flow": null
}
{"ticker":{"id":4,"code":"BBCA","name":"BANK CENTRAL ASIA Tbk","sector":null},"ksei_as_of":"2026-08-31","bing_as_of":null,"holders":[{"rank":1,"source":"ksei","name":"DWIMURIA INVESTAMA ANDALAN","entity_id":14,"investor_type":"CORPORATE","locality":"Local","shares":67729950000,"percentage_bps":5494,"signal":null},{"rank":2,"source":"ksei","name":"ANTHONI SALIM","entity_id":17,"investor_type":"INDIVIDUAL","locality":"Local","shares":1416306835,"percentage_bps":115,"signal":null},{"rank":3,"source":"ksei","name":"TRICIPTA MANDHALA GUMILANG","entity_id":16,"investor_type":"CORPORATE","locality":"Local","shares":1313250000,"percentage_bps":107,"signal":null},{"rank":4,"source":"ksei","name":"CATURGUWIRATNA SUMAPALA","entity_id":15,"investor_type":"CORPORATE","locality":"Local","shares":1261750000,"percentage_bps":102,"signal":null}],"concentration":{"top1_bps":5494,"top3_bps":5716,"hhi":3021,"free_float_bps":4182,"holder_count":4},"flow":null}
--- stderr

View file

@ -1,6 +1,6 @@
exit: 0
--- stdout
Ownership: BBCA KSEI=2026-08-31 Bing=-
Ownership: BBCA KSEI=2026-08-31 Bing=-
┌──────┬────────┬────────────────────────────┬────────────┬─────┬────────────────┬────────┬────────┐
│ RANK ┆ SOURCE ┆ INVESTOR ┆ TYPE ┆ L/F ┆ SHARES ┆ % ┆ SIGNAL │
╞══════╪════════╪════════════════════════════╪════════════╪═════╪════════════════╪════════╪════════╡
@ -12,5 +12,5 @@ exit: 0
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ 4 ┆ KSEI ┆ CATURGUWIRATNA SUMAPALA ┆ CORPORATE ┆ L ┆ 1,261,750,000 ┆ 1.02% ┆ - │
└──────┴────────┴────────────────────────────┴────────────┴─────┴────────────────┴────────┴────────┘
Concentration: top1=54.94% top3=57.16% hhi=3021 free_float=41.82% holders=4
Concentration: top1=54.94% top3=57.16% hhi=3021 free_float=41.82% holders=4
--- stderr

View file

@ -1,67 +1,4 @@
exit: 0
--- stdout
{
"ticker": {
"id": 4,
"code": "BBCA",
"name": "BANK CENTRAL ASIA Tbk",
"sector": null
},
"ksei_as_of": "2026-08-31",
"bing_as_of": null,
"holders": [
{
"rank": 1,
"source": "ksei",
"name": "DWIMURIA INVESTAMA ANDALAN",
"entity_id": 14,
"investor_type": "CORPORATE",
"locality": "Local",
"shares": 67729950000,
"percentage_bps": 5494,
"signal": null
},
{
"rank": 2,
"source": "ksei",
"name": "ANTHONI SALIM",
"entity_id": 17,
"investor_type": "INDIVIDUAL",
"locality": "Local",
"shares": 1416306835,
"percentage_bps": 115,
"signal": null
},
{
"rank": 3,
"source": "ksei",
"name": "TRICIPTA MANDHALA GUMILANG",
"entity_id": 16,
"investor_type": "CORPORATE",
"locality": "Local",
"shares": 1313250000,
"percentage_bps": 107,
"signal": null
},
{
"rank": 4,
"source": "ksei",
"name": "CATURGUWIRATNA SUMAPALA",
"entity_id": 15,
"investor_type": "CORPORATE",
"locality": "Local",
"shares": 1261750000,
"percentage_bps": 102,
"signal": null
}
],
"concentration": {
"top1_bps": 5494,
"top3_bps": 5716,
"hhi": 3021,
"free_float_bps": 4182,
"holder_count": 4
},
"flow": null
}
{"ticker":{"id":4,"code":"BBCA","name":"BANK CENTRAL ASIA Tbk","sector":null},"ksei_as_of":"2026-08-31","bing_as_of":null,"holders":[{"rank":1,"source":"ksei","name":"DWIMURIA INVESTAMA ANDALAN","entity_id":14,"investor_type":"CORPORATE","locality":"Local","shares":67729950000,"percentage_bps":5494,"signal":null},{"rank":2,"source":"ksei","name":"ANTHONI SALIM","entity_id":17,"investor_type":"INDIVIDUAL","locality":"Local","shares":1416306835,"percentage_bps":115,"signal":null},{"rank":3,"source":"ksei","name":"TRICIPTA MANDHALA GUMILANG","entity_id":16,"investor_type":"CORPORATE","locality":"Local","shares":1313250000,"percentage_bps":107,"signal":null},{"rank":4,"source":"ksei","name":"CATURGUWIRATNA SUMAPALA","entity_id":15,"investor_type":"CORPORATE","locality":"Local","shares":1261750000,"percentage_bps":102,"signal":null}],"concentration":{"top1_bps":5494,"top3_bps":5716,"hhi":3021,"free_float_bps":4182,"holder_count":4},"flow":null}
--- stderr

View file

@ -1,6 +1,6 @@
exit: 0
--- stdout
Ownership: BBCA KSEI=2026-08-31 Bing=-
Ownership: BBCA KSEI=2026-08-31 Bing=-
┌──────┬────────┬────────────────────────────┬────────────┬─────┬────────────────┬────────┬────────┐
│ RANK ┆ SOURCE ┆ INVESTOR ┆ TYPE ┆ L/F ┆ SHARES ┆ % ┆ SIGNAL │
╞══════╪════════╪════════════════════════════╪════════════╪═════╪════════════════╪════════╪════════╡
@ -12,5 +12,5 @@ exit: 0
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ 4 ┆ KSEI ┆ CATURGUWIRATNA SUMAPALA ┆ CORPORATE ┆ L ┆ 1,261,750,000 ┆ 1.02% ┆ - │
└──────┴────────┴────────────────────────────┴────────────┴─────┴────────────────┴────────┴────────┘
Concentration: top1=54.94% top3=57.16% hhi=3021 free_float=41.82% holders=4
Concentration: top1=54.94% top3=57.16% hhi=3021 free_float=41.82% holders=4
--- stderr

View file

@ -0,0 +1,4 @@
exit: 0
--- stdout
[{"symbol":"BBCA.JK","growth":{"revenue_growth":0.081,"earnings_growth":0.121,"revenue_growth_pct":8.1,"earnings_growth_pct":12.1,"revenue_signal":"slow","earnings_signal":"moderate","overall_signal":"mixed"},"valuation":{"pe_trailing":25.4,"pe_forward":null,"pb":4.6,"roe":0.198,"roe_pct":19.8,"net_margin":0.324,"net_margin_pct":32.4,"ev_ebitda":null,"pe_signal":"premium","pb_signal":"expensive","roe_signal":"strong","margin_signal":"excellent","ev_ebitda_signal":"no data","overall_signal":"expensive"},"risk":{"debt_to_equity":0.42,"current_ratio":1.18,"roa":0.031,"roa_pct":3.1,"de_signal":"conservative","current_ratio_signal":"tight","overall_signal":"moderate risk"},"overall_signal":"mixed"},{"symbol":"BBRI.JK","growth":{"revenue_growth":0.081,"earnings_growth":0.121,"revenue_growth_pct":8.1,"earnings_growth_pct":12.1,"revenue_signal":"slow","earnings_signal":"moderate","overall_signal":"mixed"},"valuation":{"pe_trailing":25.4,"pe_forward":null,"pb":4.6,"roe":0.198,"roe_pct":19.8,"net_margin":0.324,"net_margin_pct":32.4,"ev_ebitda":null,"pe_signal":"premium","pb_signal":"expensive","roe_signal":"strong","margin_signal":"excellent","ev_ebitda_signal":"no data","overall_signal":"expensive"},"risk":{"debt_to_equity":0.42,"current_ratio":1.18,"roa":0.031,"roa_pct":3.1,"de_signal":"conservative","current_ratio_signal":"tight","overall_signal":"moderate risk"},"overall_signal":"mixed"}]
--- stderr

View file

@ -0,0 +1,23 @@
exit: 0
--- stdout
Fundamental Comparison
┌────────────────┬───────────────┬───────────────┐
│ METRIC ┆ BBCA.JK ┆ BBRI.JK │
╞════════════════╪═══════════════╪═══════════════╡
│ Symbol ┆ BBCA.JK ┆ BBRI.JK │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Overall ┆ mixed ┆ mixed │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Growth ┆ mixed ┆ mixed │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Valuation ┆ expensive ┆ expensive │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Risk ┆ moderate risk ┆ moderate risk │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ P/E ┆ 25.40 ┆ 25.40 │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ ROE ┆ +19.80% ┆ +19.80% │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Revenue Growth ┆ +8.10% ┆ +8.10% │
└────────────────┴───────────────┴───────────────┘
--- stderr

View file

@ -1,81 +1,4 @@
exit: 0
--- stdout
[
{
"symbol": "BBCA.JK",
"growth": {
"revenue_growth": 0.081,
"earnings_growth": 0.121,
"revenue_growth_pct": 8.1,
"earnings_growth_pct": 12.1,
"revenue_signal": "slow",
"earnings_signal": "moderate",
"overall_signal": "mixed"
},
"valuation": {
"pe_trailing": 25.4,
"pe_forward": null,
"pb": 4.6,
"roe": 0.198,
"roe_pct": 19.8,
"net_margin": 0.324,
"net_margin_pct": 32.4,
"ev_ebitda": null,
"pe_signal": "premium",
"pb_signal": "expensive",
"roe_signal": "strong",
"margin_signal": "excellent",
"ev_ebitda_signal": "no data",
"overall_signal": "expensive"
},
"risk": {
"debt_to_equity": 0.42,
"current_ratio": 1.18,
"roa": 0.031,
"roa_pct": 3.1,
"de_signal": "conservative",
"current_ratio_signal": "tight",
"overall_signal": "moderate risk"
},
"overall_signal": "mixed"
},
{
"symbol": "BBRI.JK",
"growth": {
"revenue_growth": 0.081,
"earnings_growth": 0.121,
"revenue_growth_pct": 8.1,
"earnings_growth_pct": 12.1,
"revenue_signal": "slow",
"earnings_signal": "moderate",
"overall_signal": "mixed"
},
"valuation": {
"pe_trailing": 25.4,
"pe_forward": null,
"pb": 4.6,
"roe": 0.198,
"roe_pct": 19.8,
"net_margin": 0.324,
"net_margin_pct": 32.4,
"ev_ebitda": null,
"pe_signal": "premium",
"pb_signal": "expensive",
"roe_signal": "strong",
"margin_signal": "excellent",
"ev_ebitda_signal": "no data",
"overall_signal": "expensive"
},
"risk": {
"debt_to_equity": 0.42,
"current_ratio": 1.18,
"roa": 0.031,
"roa_pct": 3.1,
"de_signal": "conservative",
"current_ratio_signal": "tight",
"overall_signal": "moderate risk"
},
"overall_signal": "mixed"
}
]
[{"symbol":"BBCA.JK","growth":{"revenue_growth":0.081,"earnings_growth":0.121,"revenue_growth_pct":8.1,"earnings_growth_pct":12.1,"revenue_signal":"slow","earnings_signal":"moderate","overall_signal":"mixed"},"valuation":{"pe_trailing":25.4,"pe_forward":null,"pb":4.6,"roe":0.198,"roe_pct":19.8,"net_margin":0.324,"net_margin_pct":32.4,"ev_ebitda":null,"pe_signal":"premium","pb_signal":"expensive","roe_signal":"strong","margin_signal":"excellent","ev_ebitda_signal":"no data","overall_signal":"expensive"},"risk":{"debt_to_equity":0.42,"current_ratio":1.18,"roa":0.031,"roa_pct":3.1,"de_signal":"conservative","current_ratio_signal":"tight","overall_signal":"moderate risk"},"overall_signal":"mixed"},{"symbol":"BBRI.JK","growth":{"revenue_growth":0.081,"earnings_growth":0.121,"revenue_growth_pct":8.1,"earnings_growth_pct":12.1,"revenue_signal":"slow","earnings_signal":"moderate","overall_signal":"mixed"},"valuation":{"pe_trailing":25.4,"pe_forward":null,"pb":4.6,"roe":0.198,"roe_pct":19.8,"net_margin":0.324,"net_margin_pct":32.4,"ev_ebitda":null,"pe_signal":"premium","pb_signal":"expensive","roe_signal":"strong","margin_signal":"excellent","ev_ebitda_signal":"no data","overall_signal":"expensive"},"risk":{"debt_to_equity":0.42,"current_ratio":1.18,"roa":0.031,"roa_pct":3.1,"de_signal":"conservative","current_ratio_signal":"tight","overall_signal":"moderate risk"},"overall_signal":"mixed"}]
--- stderr

View file

@ -1,23 +1,23 @@
exit: 0
--- stdout
Fundamental Comparison
┌────────────────┬─────────────────────────┬─────────────────────────┐
Fundamental Comparison
┌────────────────┬───────────────┬───────────────┐
│ METRIC ┆ BBCA.JK ┆ BBRI.JK │
╞════════════════╪═════════════════════════╪═════════════════════════╡
╞════════════════╪═══════════════╪═══════════════╡
│ Symbol ┆ BBCA.JK ┆ BBRI.JK │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Overall ┆ mixed ┆ mixed │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Growth ┆ mixed ┆ mixed │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Valuation ┆ expensive ┆ expensive │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Risk ┆ moderate risk ┆ moderate risk │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Overall ┆ mixed ┆ mixed │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Growth ┆ mixed ┆ mixed │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Valuation ┆ expensive ┆ expensive │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Risk ┆ moderate risk ┆ moderate risk │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ P/E ┆ 25.40 ┆ 25.40 │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ ROE ┆ +19.80% ┆ +19.80% │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Revenue Growth ┆ +8.10% ┆ +8.10% │
└────────────────┴─────────────────────────┴─────────────────────────┘
└────────────────┴───────────────┴───────────────┘
--- stderr

View file

@ -1,22 +1,4 @@
exit: 0
--- stdout
{
"symbol": "BBCA.JK",
"eps_last_year": 1200.0,
"revenue_last_year": 100000000000.0,
"forecast": [
{
"eps_actual": null,
"eps_forecast": 1300.0,
"eps_surprise": null,
"eps_surprise_pct": null,
"revenue_actual": null,
"revenue_forecast": 110000000000.0,
"revenue_surprise": null,
"earning_release_date": "2026-03-15",
"period_type": "2026"
}
],
"history": []
}
{"symbol":"BBCA.JK","eps_last_year":1200.0,"revenue_last_year":100000000000.0,"forecast":[{"eps_actual":null,"eps_forecast":1300.0,"eps_surprise":null,"eps_surprise_pct":null,"revenue_actual":null,"revenue_forecast":110000000000.0,"revenue_surprise":null,"earning_release_date":"2026-03-15","period_type":"2026"}],"history":[]}
--- stderr

View file

@ -1,22 +1,4 @@
exit: 0
--- stdout
{
"symbol": "BBCA.JK",
"eps_last_year": 1200.0,
"revenue_last_year": 100000000000.0,
"forecast": [],
"history": [
{
"eps_actual": 320.0,
"eps_forecast": null,
"eps_surprise": 5.0,
"eps_surprise_pct": 1.5,
"revenue_actual": 27000000000.0,
"revenue_forecast": null,
"revenue_surprise": 200000000.0,
"earning_release_date": "2025-11-20",
"period_type": "Q42025"
}
]
}
{"symbol":"BBCA.JK","eps_last_year":1200.0,"revenue_last_year":100000000000.0,"forecast":[],"history":[{"eps_actual":320.0,"eps_forecast":null,"eps_surprise":5.0,"eps_surprise_pct":1.5,"revenue_actual":27000000000.0,"revenue_forecast":null,"revenue_surprise":200000000.0,"earning_release_date":"2025-11-20","period_type":"Q42025"}]}
--- stderr

View file

@ -1,56 +1,4 @@
exit: 0
--- stdout
{
"symbol": "BBCA.JK",
"eps_last_year": 1200.0,
"revenue_last_year": 100000000000.0,
"forecast": [
{
"eps_actual": null,
"eps_forecast": 1300.0,
"eps_surprise": null,
"eps_surprise_pct": null,
"revenue_actual": null,
"revenue_forecast": 110000000000.0,
"revenue_surprise": null,
"earning_release_date": "2026-03-15",
"period_type": "2026"
},
{
"eps_actual": null,
"eps_forecast": 330.0,
"eps_surprise": null,
"eps_surprise_pct": null,
"revenue_actual": null,
"revenue_forecast": 28000000000.0,
"revenue_surprise": null,
"earning_release_date": "2026-04-20",
"period_type": "Q12026"
}
],
"history": [
{
"eps_actual": 1250.0,
"eps_forecast": null,
"eps_surprise": 20.0,
"eps_surprise_pct": 1.6,
"revenue_actual": 105000000000.0,
"revenue_forecast": null,
"revenue_surprise": 500000000.0,
"earning_release_date": "2025-03-15",
"period_type": "2025"
},
{
"eps_actual": 320.0,
"eps_forecast": null,
"eps_surprise": 5.0,
"eps_surprise_pct": 1.5,
"revenue_actual": 27000000000.0,
"revenue_forecast": null,
"revenue_surprise": 200000000.0,
"earning_release_date": "2025-11-20",
"period_type": "Q42025"
}
]
}
{"symbol":"BBCA.JK","eps_last_year":1200.0,"revenue_last_year":100000000000.0,"forecast":[{"eps_actual":null,"eps_forecast":1300.0,"eps_surprise":null,"eps_surprise_pct":null,"revenue_actual":null,"revenue_forecast":110000000000.0,"revenue_surprise":null,"earning_release_date":"2026-03-15","period_type":"2026"},{"eps_actual":null,"eps_forecast":330.0,"eps_surprise":null,"eps_surprise_pct":null,"revenue_actual":null,"revenue_forecast":28000000000.0,"revenue_surprise":null,"earning_release_date":"2026-04-20","period_type":"Q12026"}],"history":[{"eps_actual":1250.0,"eps_forecast":null,"eps_surprise":20.0,"eps_surprise_pct":1.6,"revenue_actual":105000000000.0,"revenue_forecast":null,"revenue_surprise":500000000.0,"earning_release_date":"2025-03-15","period_type":"2025"},{"eps_actual":320.0,"eps_forecast":null,"eps_surprise":5.0,"eps_surprise_pct":1.5,"revenue_actual":27000000000.0,"revenue_forecast":null,"revenue_surprise":200000000.0,"earning_release_date":"2025-11-20","period_type":"Q42025"}]}
--- stderr

View file

@ -1,21 +1,4 @@
exit: 0
--- stdout
{
"instrument": {
"id": "bn91jc",
"symbol": "BBCA.JK",
"name": "Bank Central Asia Tbk PT"
},
"balance_sheet": null,
"cash_flow": null,
"income_statement": {
"values": {
"netIncome": 400000000.0,
"totalRevenue": 1000000000.0
},
"currency": "IDR",
"report_date": "2025-12-31",
"end_date": "2025-12-31"
}
}
{"instrument":{"id":"bn91jc","symbol":"BBCA.JK","name":"Bank Central Asia Tbk PT"},"balance_sheet":null,"cash_flow":null,"income_statement":{"values":{"netIncome":400000000.0,"totalRevenue":1000000000.0},"currency":"IDR","report_date":"2025-12-31","end_date":"2025-12-31"}}
--- stderr

View file

@ -1,36 +1,4 @@
exit: 0
--- stdout
{
"instrument": {
"id": "bn91jc",
"symbol": "BBCA.JK",
"name": "Bank Central Asia Tbk PT"
},
"balance_sheet": {
"values": {
"cashAndShortTermInvestments": 500000000.0,
"totalEquity": 900000000.0
},
"currency": "IDR",
"report_date": "2025-12-31",
"end_date": "2025-12-31"
},
"cash_flow": {
"values": {
"operatingCashFlow": 600000000.0
},
"currency": "IDR",
"report_date": "2025-12-31",
"end_date": "2025-12-31"
},
"income_statement": {
"values": {
"netIncome": 400000000.0,
"totalRevenue": 1000000000.0
},
"currency": "IDR",
"report_date": "2025-12-31",
"end_date": "2025-12-31"
}
}
{"instrument":{"id":"bn91jc","symbol":"BBCA.JK","name":"Bank Central Asia Tbk PT"},"balance_sheet":{"values":{"cashAndShortTermInvestments":500000000.0,"totalEquity":900000000.0},"currency":"IDR","report_date":"2025-12-31","end_date":"2025-12-31"},"cash_flow":{"values":{"operatingCashFlow":600000000.0},"currency":"IDR","report_date":"2025-12-31","end_date":"2025-12-31"},"income_statement":{"values":{"netIncome":400000000.0,"totalRevenue":1000000000.0},"currency":"IDR","report_date":"2025-12-31","end_date":"2025-12-31"}}
--- stderr

View file

@ -1,41 +1,4 @@
exit: 0
--- stdout
{
"symbol": "BBCA.JK",
"growth": {
"revenue_growth": 0.081,
"earnings_growth": 0.121,
"revenue_growth_pct": 8.1,
"earnings_growth_pct": 12.1,
"revenue_signal": "slow",
"earnings_signal": "moderate",
"overall_signal": "mixed"
},
"valuation": {
"pe_trailing": 25.4,
"pe_forward": null,
"pb": 4.6,
"roe": 0.198,
"roe_pct": 19.8,
"net_margin": 0.324,
"net_margin_pct": 32.4,
"ev_ebitda": null,
"pe_signal": "premium",
"pb_signal": "expensive",
"roe_signal": "strong",
"margin_signal": "excellent",
"ev_ebitda_signal": "no data",
"overall_signal": "expensive"
},
"risk": {
"debt_to_equity": 0.42,
"current_ratio": 1.18,
"roa": 0.031,
"roa_pct": 3.1,
"de_signal": "conservative",
"current_ratio_signal": "tight",
"overall_signal": "moderate risk"
},
"overall_signal": "mixed"
}
{"symbol":"BBCA.JK","growth":{"revenue_growth":0.081,"earnings_growth":0.121,"revenue_growth_pct":8.1,"earnings_growth_pct":12.1,"revenue_signal":"slow","earnings_signal":"moderate","overall_signal":"mixed"},"valuation":{"pe_trailing":25.4,"pe_forward":null,"pb":4.6,"roe":0.198,"roe_pct":19.8,"net_margin":0.324,"net_margin_pct":32.4,"ev_ebitda":null,"pe_signal":"premium","pb_signal":"expensive","roe_signal":"strong","margin_signal":"excellent","ev_ebitda_signal":"no data","overall_signal":"expensive"},"risk":{"debt_to_equity":0.42,"current_ratio":1.18,"roa":0.031,"roa_pct":3.1,"de_signal":"conservative","current_ratio_signal":"tight","overall_signal":"moderate risk"},"overall_signal":"mixed"}
--- stderr

View file

@ -0,0 +1,4 @@
exit: 0
--- stdout
{"symbol":"BBCA.JK","growth":{"revenue_growth":0.081,"earnings_growth":0.121,"revenue_growth_pct":8.1,"earnings_growth_pct":12.1,"revenue_signal":"slow","earnings_signal":"moderate","overall_signal":"mixed"},"valuation":{"pe_trailing":25.4,"pe_forward":null,"pb":4.6,"roe":0.198,"roe_pct":19.8,"net_margin":0.324,"net_margin_pct":32.4,"ev_ebitda":null,"pe_signal":"premium","pb_signal":"expensive","roe_signal":"strong","margin_signal":"excellent","ev_ebitda_signal":"no data","overall_signal":"expensive"},"risk":{"debt_to_equity":0.42,"current_ratio":1.18,"roa":0.031,"roa_pct":3.1,"de_signal":"conservative","current_ratio_signal":"tight","overall_signal":"moderate risk"},"overall_signal":"mixed"}
--- stderr

View file

@ -1,49 +1,49 @@
exit: 0
--- stdout
Fundamental Analysis for BBCA.JK
Fundamental Analysis for BBCA.JK
Growth Analysis for BBCA.JK
┌─────────────────┬─────────┬────────────────────┐
Growth Analysis for BBCA.JK
┌─────────────────┬─────────┬──────────┐
│ METRIC ┆ VALUE ┆ SIGNAL │
╞═════════════════╪═════════╪════════════════════╡
│ Revenue Growth ┆ +8.10% ┆ slow │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Earnings Growth ┆ +12.10% ┆ moderate │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Overall ┆ - ┆ mixed │
└─────────────────┴─────────┴────────────────────┘
╞═════════════════╪═════════╪══════════╡
│ Revenue Growth ┆ +8.10% ┆ slow │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤
│ Earnings Growth ┆ +12.10% ┆ moderate │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤
│ Overall ┆ - ┆ mixed │
└─────────────────┴─────────┴──────────┘
Valuation Analysis for BBCA.JK
┌────────────────┬─────────┬─────────────────────┐
Valuation Analysis for BBCA.JK
┌────────────────┬─────────┬───────────┐
│ METRIC ┆ VALUE ┆ SIGNAL │
╞════════════════╪═════════╪═════════════════════╡
│ P/E (Trailing) ┆ 25.40 ┆ premium │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
╞════════════════╪═════════╪═══════════╡
│ P/E (Trailing) ┆ 25.40 ┆ premium │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
│ P/E (Forward) ┆ - ┆ - │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Price/Book ┆ 4.60 ┆ expensive │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ ROE ┆ +19.80% ┆ strong │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Net Margin ┆ +32.40% ┆ excellent │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ EV/EBITDA ┆ - ┆ no data │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Overall ┆ - ┆ expensive │
└────────────────┴─────────┴─────────────────────┘
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
│ Price/Book ┆ 4.60 ┆ expensive │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
│ ROE ┆ +19.80% ┆ strong │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
│ Net Margin ┆ +32.40% ┆ excellent │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
│ EV/EBITDA ┆ - ┆ no data │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
│ Overall ┆ - ┆ expensive │
└────────────────┴─────────┴───────────┘
Risk Analysis for BBCA.JK
┌───────────────┬────────┬─────────────────────────┐
Risk Analysis for BBCA.JK
┌───────────────┬────────┬───────────────┐
│ METRIC ┆ VALUE ┆ SIGNAL │
╞═══════════════╪════════╪═════════════════════════╡
│ Debt/Equity ┆ 0.42 ┆ conservative │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Current Ratio ┆ 1.18 ┆ tight │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
╞═══════════════╪════════╪═══════════════╡
│ Debt/Equity ┆ 0.42 ┆ conservative │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Current Ratio ┆ 1.18 ┆ tight │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ ROA ┆ +3.10% ┆ - │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Overall ┆ - ┆ moderate risk │
└───────────────┴────────┴─────────────────────────┘
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Overall ┆ - ┆ moderate risk │
└───────────────┴────────┴───────────────┘
Overall Signal: mixed
Overall Signal: mixed
--- stderr

View file

@ -1,12 +1,4 @@
exit: 0
--- stdout
{
"revenue_growth": 0.081,
"earnings_growth": 0.121,
"revenue_growth_pct": 8.1,
"earnings_growth_pct": 12.1,
"revenue_signal": "slow",
"earnings_signal": "moderate",
"overall_signal": "mixed"
}
{"revenue_growth":0.081,"earnings_growth":0.121,"revenue_growth_pct":8.1,"earnings_growth_pct":12.1,"revenue_signal":"slow","earnings_signal":"moderate","overall_signal":"mixed"}
--- stderr

View file

@ -1,13 +1,13 @@
exit: 0
--- stdout
Growth Analysis for BBCA.JK
┌─────────────────┬─────────┬────────────────────┐
Growth Analysis for BBCA.JK
┌─────────────────┬─────────┬──────────┐
│ METRIC ┆ VALUE ┆ SIGNAL │
╞═════════════════╪═════════╪════════════════════╡
│ Revenue Growth ┆ +8.10% ┆ slow │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Earnings Growth ┆ +12.10% ┆ moderate │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ Overall ┆ - ┆ mixed │
└─────────────────┴─────────┴────────────────────┘
╞═════════════════╪═════════╪══════════╡
│ Revenue Growth ┆ +8.10% ┆ slow │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤
│ Earnings Growth ┆ +12.10% ┆ moderate │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤
│ Overall ┆ - ┆ mixed │
└─────────────────┴─────────┴──────────┘
--- stderr

View file

@ -1,29 +1,4 @@
exit: 0
--- stdout
[
{
"date": "2026-01-14",
"open": 8000,
"high": 8000,
"low": 8000,
"close": 8000,
"volume": 0
},
{
"date": "2026-01-15",
"open": 8075,
"high": 8075,
"low": 8075,
"close": 8075,
"volume": 0
},
{
"date": "2026-01-16",
"open": 8025,
"high": 8025,
"low": 8025,
"close": 8025,
"volume": 0
}
]
[{"date":"2026-01-14","open":8000,"high":8000,"low":8000,"close":8000,"volume":0},{"date":"2026-01-15","open":8075,"high":8075,"low":8075,"close":8075,"volume":0},{"date":"2026-01-16","open":8025,"high":8025,"low":8025,"close":8025,"volume":0}]
--- stderr

View file

@ -1,6 +1,6 @@
exit: 0
--- stdout
History for BBCA.JK
History for BBCA.JK
┌────────────┬───────┬───────┬───────┬───────┬────────┐
│ DATE ┆ OPEN ┆ HIGH ┆ LOW ┆ CLOSE ┆ VOLUME │
╞════════════╪═══════╪═══════╪═══════╪═══════╪════════╡

View file

@ -1,29 +1,4 @@
exit: 0
--- stdout
[
{
"date": "2024-03-01",
"open": 9800,
"high": 9900,
"low": 9750,
"close": 9875,
"volume": 12300000
},
{
"date": "2024-03-02",
"open": 9850,
"high": 9920,
"low": 9800,
"close": 9880,
"volume": 11000000
},
{
"date": "2024-03-03",
"open": 9860,
"high": 9950,
"low": 9820,
"close": 9925,
"volume": 14000000
}
]
[{"date":"2024-03-01","open":9800,"high":9900,"low":9750,"close":9875,"volume":12300000},{"date":"2024-03-02","open":9850,"high":9920,"low":9800,"close":9880,"volume":11000000},{"date":"2024-03-03","open":9860,"high":9950,"low":9820,"close":9925,"volume":14000000}]
--- stderr

View file

@ -0,0 +1,6 @@
exit: 0
--- stdout
{"date":"2024-03-01","open":9800,"high":9900,"low":9750,"close":9875,"volume":12300000}
{"date":"2024-03-02","open":9850,"high":9920,"low":9800,"close":9880,"volume":11000000}
{"date":"2024-03-03","open":9860,"high":9950,"low":9820,"close":9925,"volume":14000000}
--- stderr

View file

@ -1,6 +1,6 @@
exit: 0
--- stdout
History for BBCA.JK
History for BBCA.JK
┌────────────┬───────┬───────┬───────┬───────┬────────────┐
│ DATE ┆ OPEN ┆ HIGH ┆ LOW ┆ CLOSE ┆ VOLUME │
╞════════════╪═══════╪═══════╪═══════╪═══════╪════════════╡

View file

@ -1,16 +1,4 @@
exit: 0
--- stdout
{
"id": "bn91jc",
"symbol": "BBCA.JK",
"summary": "Mixed analyst signals for Bank Central Asia Tbk PT: 1 positive, 1 negative, 1 neutral.",
"highlights": [
"P/E ratio: Earnings data unavailable, can't compare P/E ratio with peers",
"Analyst price target: Analysts forecast more than 20% upside for 12-month price target"
],
"risks": [
"Quarterly Revenue YoY Growth: Revenue grew worse than peers"
],
"last_updated": "2026-03-26T04:14:57.9197955Z"
}
{"id":"bn91jc","symbol":"BBCA.JK","summary":"Mixed analyst signals for Bank Central Asia Tbk PT: 1 positive, 1 negative, 1 neutral.","highlights":["P/E ratio: Earnings data unavailable, can't compare P/E ratio with peers","Analyst price target: Analysts forecast more than 20% upside for 12-month price target"],"risks":["Quarterly Revenue YoY Growth: Revenue grew worse than peers"],"last_updated":"2026-03-26T04:14:57.9197955Z"}
--- stderr

View file

@ -1,15 +1,4 @@
exit: 0
--- stdout
[
{
"id": "news-1",
"symbol": "BBCA.JK",
"title": "BCA reports steady growth",
"url": "https://example.com/news/bca-growth",
"description": "BCA delivered steady quarterly growth across lending and deposits.",
"provider": "Contoso News",
"published_at": "2026-03-20T10:00:00Z",
"read_time_min": 3
}
]
[{"id":"news-1","symbol":"BBCA.JK","title":"BCA reports steady growth","url":"https://example.com/news/bca-growth","description":"BCA delivered steady quarterly growth across lending and deposits.","provider":"Contoso News","published_at":"2026-03-20T10:00:00Z","read_time_min":3}]
--- stderr

View file

@ -1,27 +1,4 @@
exit: 0
--- stdout
{
"id": "feee16af-c94c-465d-a59d-a9727c22bd0e",
"symbol": "BBCA",
"short_name": "Bank Cntrl Asia",
"long_name": "PT Bank Central Asia Tbk",
"description": "PT Bank Central Asia Tbk (the Bank) is an Indonesia-based commercial bank.",
"sector": "Financials",
"industry": "Banking Services",
"website": "https://www.bca.co.id/",
"employees": 27937,
"address": "Menara BCA, Grand, Jl. MH Thamrin No. 1",
"city": "JAKARTA PUSAT",
"country": "Indonesia",
"phone": "+62 21 23588000",
"officers": [
{
"name": "Jahja Setiaatmadja",
"title": "President Director",
"age": 69,
"year_born": 1955,
"total_pay": 12000000000
}
]
}
{"id":"feee16af-c94c-465d-a59d-a9727c22bd0e","symbol":"BBCA","short_name":"Bank Cntrl Asia","long_name":"PT Bank Central Asia Tbk","description":"PT Bank Central Asia Tbk (the Bank) is an Indonesia-based commercial bank.","sector":"Financials","industry":"Banking Services","website":"https://www.bca.co.id/","employees":27937,"address":"Menara BCA, Grand, Jl. MH Thamrin No. 1","city":"JAKARTA PUSAT","country":"Indonesia","phone":"+62 21 23588000","officers":[{"name":"Jahja Setiaatmadja","title":"President Director","age":69,"year_born":1955,"total_pay":12000000000}]}
--- stderr

View file

@ -1,35 +1,4 @@
exit: 0
--- stdout
[
{
"symbol": "BBCA.JK",
"price": 9875,
"change": 117,
"change_pct": 1.2,
"volume": 12300000,
"market_cap": 1215200000000000,
"week52_high": 10250,
"week52_low": 7800,
"week52_position": 0.8469387755102041,
"range_signal": "upper",
"prev_close": 9758,
"avg_volume": 10000000,
"as_of": null
},
{
"symbol": "BBRI.JK",
"price": 9875,
"change": 117,
"change_pct": 1.2,
"volume": 12300000,
"market_cap": 1215200000000000,
"week52_high": 10250,
"week52_low": 7800,
"week52_position": 0.8469387755102041,
"range_signal": "upper",
"prev_close": 9758,
"avg_volume": 10000000,
"as_of": null
}
]
[{"symbol":"BBCA.JK","price":9875,"change":117,"change_pct":1.2,"volume":12300000,"market_cap":1215200000000000,"week52_high":10250,"week52_low":7800,"week52_position":0.8469387755102041,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null},{"symbol":"BBRI.JK","price":9875,"change":117,"change_pct":1.2,"volume":12300000,"market_cap":1215200000000000,"week52_high":10250,"week52_low":7800,"week52_position":0.8469387755102041,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null}]
--- stderr

View file

@ -0,0 +1,5 @@
exit: 0
--- stdout
{"symbol":"BBCA.JK","price":9875,"change":117,"change_pct":1.2,"volume":12300000,"market_cap":1215200000000000,"week52_high":10250,"week52_low":7800,"week52_position":0.8469387755102041,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null}
{"symbol":"BBRI.JK","price":9875,"change":117,"change_pct":1.2,"volume":12300000,"market_cap":1215200000000000,"week52_high":10250,"week52_low":7800,"week52_position":0.8469387755102041,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null}
--- stderr

View file

@ -1,20 +1,4 @@
exit: 0
--- stdout
[
{
"symbol": "BBCA.JK",
"price": 9875,
"change": 117,
"change_pct": 1.2,
"volume": 12300000,
"market_cap": 1215200000000000,
"week52_high": 10250,
"week52_low": 7800,
"week52_position": 0.8469387755102041,
"range_signal": "upper",
"prev_close": 9758,
"avg_volume": 10000000,
"as_of": null
}
]
[{"symbol":"BBCA.JK","price":9875,"change":117,"change_pct":1.2,"volume":12300000,"market_cap":1215200000000000,"week52_high":10250,"week52_low":7800,"week52_position":0.8469387755102041,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null}]
--- stderr

View file

@ -0,0 +1,4 @@
exit: 0
--- stdout
[{"symbol":"BBCA.JK","price":9875,"change":117,"change_pct":1.2,"volume":12300000,"market_cap":1215200000000000,"week52_high":10250,"week52_low":7800,"week52_position":0.8469387755102041,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null},{"symbol":"BBRI.JK","price":9875,"change":117,"change_pct":1.2,"volume":12300000,"market_cap":1215200000000000,"week52_high":10250,"week52_low":7800,"week52_position":0.8469387755102041,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null}]
--- stderr

View file

@ -0,0 +1,10 @@
exit: 0
--- stdout
┌─────────┬───────┬──────┬────────┬────────────┬───────────────────────┬────────────┐
│ SYMBOL ┆ PRICE ┆ CHG ┆ CHG% ┆ VOLUME ┆ MKT CAP ┆ 52W RANGE │
╞═════════╪═══════╪══════╪════════╪════════════╪═══════════════════════╪════════════╡
│ BBCA.JK ┆ 9,875 ┆ +117 ┆ +1.20% ┆ 12,300,000 ┆ 1,215,200,000,000,000 ┆ ████████░░ │
├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ BBRI.JK ┆ 9,875 ┆ +117 ┆ +1.20% ┆ 12,300,000 ┆ 1,215,200,000,000,000 ┆ ████████░░ │
└─────────┴───────┴──────┴────────┴────────────┴───────────────────────┴────────────┘
--- stderr

Some files were not shown because too many files have changed in this diff Show more