A BBCA Market Analysis Dashboard provides a practical way to analyze several aspects of BBCA in one Python workflow.
This project uses three IDX API endpoints: getSeasonality, getRunningTrade, and getForeignOwnership. Seasonality analyzes BBCA using 2026 data with five years of historical reference, Running Trade retrieves BBCA transactions from February 11, 2026, and Foreign Ownership provides institutional ownership information.
The notebook consists of five cells covering API configuration, reusable request functions, data retrieval, response normalization, and a human-readable dashboard.
Cell 1 — Import Library and Configure RapidAPI
# ============================================================
# CELL 1 — IMPORT LIBRARY & KONFIGURASI RAPIDAPI
# ============================================================
import requests
import pandas as pd
import json
import time
from datetime import datetime
pd.set_option("display.max_columns", None)
pd.set_option("display.max_rows", 100)
pd.set_option("display.max_colwidth", 100)
# ============================================================
# RAPIDAPI CONFIG
# ============================================================
RAPIDAPI_KEY = "YOUR_RAPIDAPI_KEY"
BASE_URL = "https://indonesia-stock-exchange-idx.p.rapidapi.com"
HEADERS = {
"Content-Type": "application/json",
"x-rapidapi-host": "indonesia-stock-exchange-idx.p.rapidapi.com",
"x-rapidapi-key": RAPIDAPI_KEY
}
print("=" * 100)
print("IDX API CONFIGURATION")
print("=" * 100)
print("Base URL :", BASE_URL)
print("Host :", HEADERS["x-rapidapi-host"])
print("Status : Konfigurasi siap")
Cell 1 prepares the required libraries and RapidAPI configuration used throughout the notebook.
Cell 2 — Create API Request and Data Helper Functions
# ============================================================
# CELL 2 — FUNCTION REQUEST API
# ============================================================
def request_api(name, endpoint, params=None):
"""
Melakukan GET request ke IDX RapidAPI
dengan error handling.
"""
url = BASE_URL + endpoint
print("\n" + "=" * 100)
print(name)
print("-" * 100)
print("Endpoint :", endpoint)
try:
response = requests.get(
url,
headers=HEADERS,
params=params,
timeout=30
)
print("Request URL :", response.url)
print("Status :", response.status_code)
try:
result = response.json()
except Exception:
result = {
"success": False,
"message": response.text
}
if response.status_code == 200:
print("Success :", result.get("success", True) if isinstance(result, dict) else True)
print("Pesan : Request berhasil")
else:
print("Success : False")
if isinstance(result, dict):
message = (
result.get("message")
or result.get("error")
or f"HTTP {response.status_code}"
)
else:
message = f"HTTP {response.status_code}"
print("Pesan :", message)
return result
except requests.exceptions.Timeout:
print("Status : TIMEOUT")
print("Success : False")
print("Pesan : Request melebihi batas waktu.")
return {
"success": False,
"message": "Request timeout",
"data": None
}
except requests.exceptions.RequestException as e:
print("Status : REQUEST ERROR")
print("Success : False")
print("Pesan :", str(e))
return {
"success": False,
"message": str(e),
"data": None
}
def extract_data(response):
"""
Mengambil data dari response API secara aman.
"""
if response is None:
return []
if isinstance(response, list):
return response
if not isinstance(response, dict):
return []
data = response.get("data")
if data is None:
return []
return data
def safe_dataframe(data):
"""
Mengubah berbagai struktur data menjadi DataFrame
tanpa menyebabkan error.
"""
if data is None:
return pd.DataFrame()
if isinstance(data, list):
if len(data) == 0:
return pd.DataFrame()
if all(isinstance(x, dict) for x in data):
return pd.json_normalize(data)
return pd.DataFrame({"value": data})
if isinstance(data, dict):
try:
return pd.json_normalize(data)
except Exception:
return pd.DataFrame([data])
return pd.DataFrame({"value": [data]})
print("Function request_api(), extract_data(), dan safe_dataframe() siap digunakan.")
Cell 2 creates reusable functions for API requests, safe data extraction, and DataFrame conversion. It also handles request timeouts and request errors.
Cell 3 — Request Seasonality Running Trade and Foreign Ownership
# ============================================================
# CELL 3 — REQUEST SEASONALITY, RUNNING TRADE & FOREIGN OWNERSHIP
# ============================================================
# ------------------------------------------------------------
# 1. GET SEASONALITY
# ------------------------------------------------------------
seasonality_response = request_api(
"getSeasonality",
"/api/emiten/BBCA/seasonality",
params={
"year": 2026,
"backYear": 5
}
)
print("\n⏳ Memberikan jeda 3 detik sebelum request berikutnya...")
time.sleep(3)
# ------------------------------------------------------------
# 2. GET RUNNING TRADE
# ------------------------------------------------------------
running_trade_response = request_api(
"getRunningTrade",
"/api/emiten/running-trade",
params={
"sort": "ASC",
"actionType": "RUNNING_TRADE_ACTION_TYPE_ALL",
"date": "2026-02-11",
"marketBoard": "BOARD_TYPE_REGULAR",
"limit": 50,
"orderBy": "RUNNING_TRADE_ORDER_BY_TIME",
"symbols": "BBCA"
}
)
print("\n⏳ Memberikan jeda 3 detik sebelum request berikutnya...")
time.sleep(3)
# ------------------------------------------------------------
# 3. GET FOREIGN OWNERSHIP
# ------------------------------------------------------------
foreign_ownership_response = request_api(
"getForeignOwnership",
"/api/emiten/BBCA/foreign-ownership"
)
# ------------------------------------------------------------
# EXTRACT DATA
# ------------------------------------------------------------
seasonality_data = extract_data(seasonality_response)
running_trade_data = extract_data(running_trade_response)
foreign_ownership_data = extract_data(foreign_ownership_response)
print("\n" + "=" * 100)
print("SEMUA REQUEST SELESAI")
print("=" * 100)
Cell 3 retrieves the three datasets for BBCA and applies a three-second delay between API calls.
Cell 4 — Normalize and Inspect API Responses
# ============================================================
# CELL 4 — NORMALISASI & PEMERIKSAAN STRUKTUR RESPONSE
# ============================================================
seasonality_df = safe_dataframe(seasonality_data)
running_trade_df = safe_dataframe(running_trade_data)
foreign_ownership_df = safe_dataframe(foreign_ownership_data)
def response_info(name, response):
print(f"\n{name}:")
print("Tipe :", type(response).__name__)
if isinstance(response, dict):
print("Keys :", list(response.keys()))
data = response.get("data")
print("Tipe data :", type(data).__name__)
if isinstance(data, dict):
print("Data keys :", list(data.keys()))
elif isinstance(data, list):
print("Jumlah item data :", len(data))
if len(data) > 0 and isinstance(data[0], dict):
print("Sample keys :", list(data[0].keys()))
print("=" * 100)
print("HASIL NORMALISASI")
print("=" * 100)
print("Seasonality Records :", len(seasonality_df))
print("Running Trade Records :", len(running_trade_df))
print("Foreign Ownership Records :", len(foreign_ownership_df))
print("\n" + "=" * 100)
print("STRUKTUR RESPONSE")
print("=" * 100)
response_info("Seasonality", seasonality_response)
response_info("Running Trade", running_trade_response)
response_info("Foreign Ownership", foreign_ownership_response)
print("\n" + "=" * 100)
print("KOLOM DATAFRAME")
print("=" * 100)
print("\nSeasonality:")
print(seasonality_df.columns.tolist())
print("\nRunning Trade:")
print(running_trade_df.columns.tolist())
print("\nForeign Ownership:")
print(foreign_ownership_df.columns.tolist())
Cell 4 normalizes the API responses and checks record counts, response structure, data types, keys, and available columns.
Cell 5 — Build the Human Readable BBCA Dashboard
# ============================================================
# CELL 5 — IDX BBCA DASHBOARD (HUMAN READABLE)
# ============================================================
from datetime import datetime
import pandas as pd
def safe_num(value, default=0):
try:
if value is None:
return default
value = str(value).replace(",", "").replace("%", "").strip()
if value in ["", "-", "None", "nan"]:
return default
return float(value)
except:
return default
def get_status(response):
if not isinstance(response, dict):
return "Berhasil" if response else "Tidak Ada Data"
if response.get("success") is False:
return "Gagal"
if response.get("data") is not None:
return "Berhasil"
return "Tidak Ada Data"
# ============================================================
# HEADER
# ============================================================
print("=" * 100)
print("IDX BBCA SEASONALITY, RUNNING TRADE & FOREIGN OWNERSHIP DASHBOARD")
print("=" * 100)
# ============================================================
# 1. SEASONALITY
# ============================================================
print("\n📅 SEASONALITY BBCA")
print("-" * 100)
season_data = (
seasonality_response.get("data", {})
if isinstance(seasonality_response, dict)
else {}
)
price_change = (
season_data.get("price_change", [])
if isinstance(season_data, dict)
else []
)
if isinstance(price_change, list) and len(price_change) > 0:
print(f"Jumlah Tahun Analisis : {len(price_change)}")
print("Status :", get_status(seasonality_response))
print()
for year_data in price_change:
if not isinstance(year_data, dict):
continue
year = year_data.get("row", "-")
columns = year_data.get("columns", [])
yearly_change = None
monthly_values = []
for item in columns:
if not isinstance(item, dict):
continue
name = item.get("name", "-")
value = item.get("value", "-")
if name == "Year":
yearly_change = value
else:
monthly_values.append((name, value))
print(f"📌 Tahun {year}")
if yearly_change is not None:
change_num = safe_num(yearly_change)
if change_num > 0:
kondisi = "NAIK 🟢"
elif change_num < 0:
kondisi = "TURUN 🔴"
else:
kondisi = "STABIL ⚪"
print(
f"Perubahan Tahunan : {yearly_change}% "
f"({kondisi})"
)
print("Performa Bulanan :")
for month, value in monthly_values:
num = safe_num(value)
if num > 0:
trend = "🟢"
elif num < 0:
trend = "🔴"
else:
trend = "⚪"
print(
f" {month:<4} : "
f"{str(value):>8}% {trend}"
)
print()
else:
print("Tidak ada data seasonality yang tersedia.")
# ============================================================
# 2. RUNNING TRADE
# ============================================================
print("\n📈 RUNNING TRADE BBCA — 11 FEBRUARI 2026")
print("-" * 100)
running_data = (
running_trade_response.get("data", {})
if isinstance(running_trade_response, dict)
else {}
)
running_trade = []
is_open_market = None
if isinstance(running_data, dict):
running_trade = running_data.get(
"running_trade",
[]
)
is_open_market = running_data.get(
"is_open_market"
)
if not isinstance(running_trade, list):
running_trade = []
print(
"Status Pasar :",
"BUKA" if is_open_market else "TUTUP"
)
print(
"Jumlah Transaksi :",
len(running_trade)
)
if len(running_trade) > 0:
buy_count = 0
sell_count = 0
total_lot = 0
total_value = 0
prices = []
for trade in running_trade:
if not isinstance(trade, dict):
continue
action = str(
trade.get("action", "")
).lower()
price = safe_num(
trade.get("price", 0)
)
lot = safe_num(
trade.get("lot", 0)
)
if action == "buy":
buy_count += 1
elif action == "sell":
sell_count += 1
total_lot += lot
total_value += (
price *
lot *
100
)
if price > 0:
prices.append(price)
print(
"Transaksi Buy :",
f"{buy_count} transaksi"
)
print(
"Transaksi Sell :",
f"{sell_count} transaksi"
)
print(
"Total Volume :",
f"{total_lot:,.0f} lot"
)
print(
"Estimasi Nilai :",
f"Rp {total_value:,.0f}"
)
if prices:
print(
"Harga Tertinggi :",
f"Rp {max(prices):,.0f}"
)
print(
"Harga Terendah :",
f"Rp {min(prices):,.0f}"
)
print("\n10 TRANSAKSI TERBARU")
print("-" * 100)
for i, trade in enumerate(
running_trade[:10],
start=1
):
time_trade = trade.get(
"time",
"-"
)
action = str(
trade.get(
"action",
"-"
)
).upper()
price = trade.get(
"price",
"-"
)
lot = trade.get(
"lot",
"-"
)
buyer = trade.get(
"buyer",
"-"
)
seller = trade.get(
"seller",
"-"
)
print(
f"{i:02d}. "
f"{time_trade} | "
f"{action:<4} | "
f"Rp {price:<10} | "
f"{lot} lot"
)
print(
f" Buyer : {buyer}"
)
print(
f" Seller: {seller}"
)
else:
print("Tidak ada running trade yang tersedia.")
# ============================================================
# 3. FOREIGN OWNERSHIP
# ============================================================
print("\n\n🌏 FOREIGN OWNERSHIP BBCA")
print("-" * 100)
foreign_data = (
foreign_ownership_response.get("data", {})
if isinstance(foreign_ownership_response, dict)
else {}
)
if isinstance(foreign_data, dict):
symbol = foreign_data.get(
"symbol",
"BBCA"
)
institution = foreign_data.get(
"institutionOwnership",
{}
)
if not isinstance(institution, dict):
institution = {}
holders = institution.get(
"holders",
[]
)
if not isinstance(holders, list):
holders = []
total_pct = institution.get(
"totalPctHeld",
0
)
institution_count = institution.get(
"count",
len(holders)
)
print(
"Emiten :",
symbol.replace(".JK", "")
)
print(
"Jumlah Institusi :",
institution_count
)
print(
"Total Kepemilikan :",
f"{safe_num(total_pct):,.2f}%"
)
if len(holders) > 0:
print("\nDAFTAR PEMEGANG SAHAM INSTITUSI")
print("-" * 100)
for i, holder in enumerate(
holders,
start=1
):
if not isinstance(holder, dict):
continue
organization = holder.get(
"organization",
"-"
)
pct_held = holder.get(
"pctHeld",
0
)
position = (
holder.get("position")
or holder.get("shares")
or holder.get("reportedHolding")
or "-"
)
print(
f"{i:02d}. {organization}"
)
print(
f" Kepemilikan : "
f"{safe_num(pct_held):,.4f}%"
)
if position != "-":
print(
f" Posisi : {position}"
)
else:
print(
"\nTidak terdapat detail "
"pemegang saham institusi."
)
else:
print(
"Tidak ada data foreign ownership."
)
# ============================================================
# INTERPRETASI SEDERHANA
# ============================================================
print("\n\n" + "=" * 100)
print("INTERPRETASI SEDERHANA")
print("=" * 100)
# Seasonality terbaru
if isinstance(price_change, list) and len(price_change) > 0:
latest_year = price_change[0]
if isinstance(latest_year, dict):
annual_value = None
for item in latest_year.get(
"columns",
[]
):
if (
isinstance(item, dict)
and item.get("name") == "Year"
):
annual_value = safe_num(
item.get("value")
)
if annual_value is not None:
if annual_value > 0:
print(
"📅 Seasonality : "
"Performa tahunan BBCA sedang positif."
)
elif annual_value < 0:
print(
"📅 Seasonality : "
"Performa tahunan BBCA sedang negatif."
)
else:
print(
"📅 Seasonality : "
"Performa tahunan relatif stabil."
)
# Running trade
if len(running_trade) > 0:
if buy_count > sell_count:
print(
"📈 Running Trade : "
"Frekuensi transaksi BUY lebih dominan."
)
elif sell_count > buy_count:
print(
"📉 Running Trade : "
"Frekuensi transaksi SELL lebih dominan."
)
else:
print(
"⚖️ Running Trade : "
"Frekuensi BUY dan SELL relatif seimbang."
)
# Foreign ownership
if isinstance(foreign_data, dict):
print(
"🌏 Foreign Ownership : "
f"Terdeteksi {institution_count} institusi "
"dalam data kepemilikan yang tersedia."
)
# ============================================================
# RINGKASAN
# ============================================================
print("\n\n" + "=" * 100)
print("RINGKASAN")
print("=" * 100)
print(
f"📅 Seasonality : "
f"{len(price_change) if isinstance(price_change, list) else 0} tahun"
)
print(
f"📈 Running Trade : "
f"{len(running_trade)} transaksi"
)
print(
f"🌏 Foreign Ownership : "
f"{institution_count if isinstance(foreign_data, dict) else 0} institusi"
)
print()
print(
"Status Seasonality :",
get_status(seasonality_response)
)
print(
"Status Running Trade :",
get_status(running_trade_response)
)
print(
"Status Foreign Ownership :",
get_status(foreign_ownership_response)
)
print(
"\nSelesai diproses :",
datetime.now().strftime(
"%d-%m-%Y %H:%M:%S"
)
)
Cell 5 converts the three datasets into a more readable dashboard. It summarizes Seasonality, Running Trade activity, institutional ownership, simple interpretations, API status, and processing time.
Final Result
The completed BBCA Market Analysis Dashboard combines historical price behavior, transaction activity, and institutional ownership in one workflow.
Seasonality provides annual and monthly performance patterns. Running Trade summarizes buy and sell activity, volume, estimated transaction value, recent trades, and price range. Foreign Ownership provides institutional ownership data and available holder details.
Conclusion
The BBCA Market Analysis Dashboard demonstrates how Python and RapidAPI can combine Seasonality, Running Trade, and Foreign Ownership into a complete five-cell Google Colab project.
The workflow covers API configuration, reusable request functions, data retrieval, normalization, transaction analysis, ownership analysis, and a final human-readable dashboard while keeping the notebook structure intact.
