OHLC.dev editorialIDX

IDX Insider Analysis Using Python

This article explains how to build an IDX market intelligence dashboard using Python and the IDX API to analyze insider transactions retail sentiment bandar or smart money activity broker flows and IPO momentum. The project uses five Python cells covering API configuration API requests robust JSON parsing data extraction and final dashboard presentation.

August 22, 202616 min readRafatar
IDX Insider Analysis Using Python

Introduction

Understanding the Indonesian stock market requires more than simply looking at price movements.

Market participants can examine several additional signals, including insider transactions, retail investor behaviour, smart-money activity, broker flows, and IPO momentum. When these datasets are combined into a single analytical workflow, they can provide a broader picture of current market sentiment.

This project builds an IDX Insider, Retail-Bandar Sentiment & IPO Momentum Dashboard using Python.

The dashboard retrieves three different types of analysis from the Indonesia Stock Exchange IDX API:

  1. Insider Screening

  2. Retail-Bandar Sentiment

  3. IPO Momentum

The API responses are then processed using Python, JSON parsing, and Pandas before being presented as a structured terminal-style dashboard.

The original notebook is organized into five cells covering configuration, API requests, JSON parsing, data extraction, and the final dashboard.


Project Overview

The project uses three API analysis endpoints.

Insider Screening

The insider screening component analyses insider transactions for:

  • BBCA

  • BUMI

  • ADRO

The configured analysis period is December 2025.

The output can contain information such as:

  • Buy shares

  • Sell shares

  • Net shares

  • Buy value

  • Sell value

  • Net value

  • Transaction counts

  • Unique insiders

  • Last activity

  • Dominant action


Retail-Bandar Sentiment

The sentiment analysis focuses on BBCA over a seven-day period.

The project separates sentiment indicators into two categories:

Retail indicators

  • Frequency score

  • Small lot percentage

  • FOMO score

  • Volume participation

Bandar / Smart Money indicators

  • Large lot percentage

  • Accumulation score

  • Foreign flow

  • Institutional flow

The project also extracts the top broker buyers and sellers.


IPO Momentum

The third component retrieves IPO momentum data from the API.

The dashboard attempts to identify:

  • IPO sentiment

  • Momentum

  • Momentum score

The sentiment can be categorized as:

  • BULLISH

  • BEARISH

  • NEUTRAL


Cell 1 — Import & Configuration

import requests
import pandas as pd
import json
from datetime import datetime

RAPIDAPI_KEY = "YOUR_API_KEY"
HOST = "indonesia-stock-exchange-idx.p.rapidapi.com"

HEADERS = {
    "Content-Type": "application/json",
    "x-rapidapi-host": HOST,
    "x-rapidapi-key": RAPIDAPI_KEY
}

SYMBOLS = "BBCA,BUMI,ADRO"
PERIOD = "2025-12"
SENTIMENT_SYMBOL = "BBCA"
SENTIMENT_DAYS = 7

print("=" * 100)
print("IDX INSIDER, RETAIL BANDAR SENTIMENT & IPO MOMENTUM ANALYSIS")
print("=" * 100)

Explanation

Cell 1 prepares the Python environment and defines the main configuration parameters.

The project imports four libraries:

  • requests for communicating with the API.

  • pandas for data processing.

  • json for decoding JSON responses.

  • datetime for generating the dashboard update timestamp.

The HEADERS dictionary contains the information required by RapidAPI.

For security, the API key should not be publicly exposed in an article or GitHub repository. The original notebook contains an API key, so it should be revoked or rotated if it has been shared publicly.

The main configuration then defines the stocks and analysis periods.

SYMBOLS contains BBCA, BUMI, and ADRO for insider analysis.

SENTIMENT_SYMBOL defines BBCA as the stock used for retail-bandar sentiment analysis, while SENTIMENT_DAYS = 7 sets a seven-day sentiment window.


Cell 2 — Request Three APIs

def api_get(url, params=None):
    try:
        response = requests.get(
            url,
            headers=HEADERS,
            params=params,
            timeout=30
        )

        try:
            data = response.json()
        except:
            data = {"raw_response": response.text}

        return {
            "status_code": response.status_code,
            "success": response.ok,
            "data": data
        }

    except Exception as e:
        return {
            "status_code": None,
            "success": False,
            "data": {"error": str(e)}
        }


insider_result = api_get(
    "https://indonesia-stock-exchange-idx.p.rapidapi.com/api/analysis/insider-screening",
    {
        "symbols": SYMBOLS,
        "action_type": "ACTION_TYPE_UNSPECIFIED",
        "page": 1,
        "limit": 100,
        "source_type": "SOURCE_TYPE_UNSPECIFIED",
        "period": PERIOD
    }
)

sentiment_result = api_get(
    f"https://indonesia-stock-exchange-idx.p.rapidapi.com/api/analysis/sentiment/{SENTIMENT_SYMBOL}",
    {
        "days": SENTIMENT_DAYS
    }
)

ipo_result = api_get(
    "https://indonesia-stock-exchange-idx.p.rapidapi.com/api/analysis/sentiment/ipo/momentum"
)

print("=" * 100)
print("STATUS REQUEST")
print("-" * 100)
print(f"Insider Screening       : HTTP {insider_result['status_code']}")
print(f"Retail Bandar Sentiment : HTTP {sentiment_result['status_code']}")
print(f"IPO Momentum            : HTTP {ipo_result['status_code']}")
print("=" * 100)

Explanation

Cell 2 creates the API request layer for the project.

Instead of writing three separate request implementations, the notebook defines a reusable api_get() function.

The function accepts:

  • url

  • params

It then sends a GET request with the configured headers.

The function also uses a try/except structure to prevent the entire notebook from stopping when a request fails.

If the response contains valid JSON, it is parsed using:

response.json()

If the response is not valid JSON, the raw response text is preserved.

The returned dictionary contains three important fields:

  • status_code

  • success

  • data

This structure allows the rest of the notebook to process API results consistently.

The notebook then performs three API requests.

Insider API

The first request uses the insider-screening endpoint and passes the configured symbols and period.

Sentiment API

The second request retrieves sentiment information for BBCA using the seven-day period.

IPO Momentum API

The third request retrieves the IPO momentum analysis without additional parameters.

Finally, the notebook prints the HTTP status code for all three requests, making it easy to determine whether the APIs responded successfully.


Cell 3 — Robust JSON Parser

# ============================================================
# CELL 3 — ROBUST JSON PARSER
# ============================================================

def decode_json(value):
    if isinstance(value, str):
        text = value.strip()

        if text.startswith("{") or text.startswith("["):
            try:
                return decode_json(json.loads(text))
            except:
                return value

    if isinstance(value, dict):
        return {
            k: decode_json(v)
            for k, v in value.items()
        }

    if isinstance(value, list):
        return [
            decode_json(v)
            for v in value
        ]

    return value


def find_objects(obj, required_keys=None):
    """
    Mencari semua dictionary yang memiliki key tertentu
    di seluruh struktur JSON.
    """

    found = []

    if isinstance(obj, dict):

        if required_keys:
            if all(key in obj for key in required_keys):
                found.append(obj)

        for value in obj.values():
            found.extend(
                find_objects(value, required_keys)
            )

    elif isinstance(obj, list):

        for item in obj:
            found.extend(
                find_objects(item, required_keys)
            )

    return found


def first_object(obj, required_keys):
    found = find_objects(
        obj,
        required_keys
    )

    if found:
        return found[0]

    return {}


def get_status(result):

    code = result.get("status_code")

    if result.get("success"):
        return "Berhasil"

    if code == 403:
        return "403 - Forbidden"

    if code == 429:
        return "429 - Rate Limit"

    if code == 422:
        return "422 - Parameter Error"

    if code is None:
        return "Connection Error"

    return f"HTTP {code}"


def format_number(value):

    if value is None or value == "":
        return "-"

    try:
        return f"{float(value):,.0f}"
    except:
        return str(value)


def format_billion(value):

    if value is None or value == "":
        return "-"

    try:
        return f"Rp {float(value) / 1_000_000_000:,.2f} B"
    except:
        return str(value)


def format_percent(value):

    if value is None or value == "":
        return "-"

    try:
        return f"{float(value):,.1f}%"
    except:
        return str(value)

Explanation

Cell 3 is one of the most important parts of the project because API responses are not always guaranteed to have a simple or consistent structure.

The decode_json() function recursively examines the response.

If a value is a string that appears to contain JSON, the function attempts to decode it.

If the value is a dictionary, every value inside the dictionary is processed recursively.

If the value is a list, every item is processed recursively.

This makes the parser capable of handling nested JSON structures.

The find_objects() function then searches recursively for dictionaries containing specific keys.

For example, the insider analysis searches for objects containing:

  • symbol

  • totalBuyShares

  • totalSellShares

  • netShares

This is useful when the required data is buried several levels deep inside an API response.

The first_object() helper returns the first matching object found.

The get_status() function converts HTTP status information into readable messages such as:

  • Success

  • 403 - Forbidden

  • 429 - Rate Limit

  • 422 - Parameter Error

  • Connection Error

Finally, three formatting helpers prepare numerical output:

  • format_number() formats share quantities.

  • format_billion() converts monetary values into billions of Rupiah.

  • format_percent() formats percentage values.


Cell 4 — Data Extraction

# ============================================================
# CELL 4 — EXTRACT DATA
# ============================================================

# ============================================================
# RAW DATA
# ============================================================

insider_raw = decode_json(
    insider_result.get("data")
)

sentiment_raw = decode_json(
    sentiment_result.get("data")
)

ipo_raw = decode_json(
    ipo_result.get("data")
)


# ============================================================
# INSIDER SCREENING
# ============================================================

insider_objects = find_objects(
    insider_raw,
    [
        "symbol",
        "totalBuyShares",
        "totalSellShares",
        "netShares"
    ]
)

insider_rows = []

for item in insider_objects:

    insider_rows.append({
        "Symbol": item.get("symbol", "-"),

        "Buy Shares": item.get(
            "totalBuyShares", 0
        ),

        "Sell Shares": item.get(
            "totalSellShares", 0
        ),

        "Net Shares": item.get(
            "netShares", 0
        ),

        "Buy Value": item.get(
            "totalBuyValue", 0
        ),

        "Sell Value": item.get(
            "totalSellValue", 0
        ),

        "Net Value": item.get(
            "netValue", 0
        ),

        "Buy Count": item.get(
            "buyCount", 0
        ),

        "Sell Count": item.get(
            "sellCount", 0
        ),

        "Insiders": item.get(
            "uniqueInsiders", []
        ),

        "Last Activity": item.get(
            "lastActivity", "-"
        ),

        "Action": item.get(
            "dominantAction", "-"
        )
    })


insider_df = pd.DataFrame(
    insider_rows
)


# ============================================================
# RETAIL
# ============================================================

retail_objects = find_objects(
    sentiment_raw,
    [
        "score",
        "status",
        "indicators",
        "danger_level"
    ]
)

retail = {}

for item in retail_objects:

    indicators = item.get(
        "indicators",
        {}
    )

    if (
        isinstance(indicators, dict)
        and
        (
            "frequency_score" in indicators
            or
            "small_lot_percentage" in indicators
            or
            "fomo_score" in indicators
        )
    ):

        retail = item
        break


# ============================================================
# BANDAR
# ============================================================

bandar_objects = find_objects(
    sentiment_raw,
    [
        "score",
        "status",
        "indicators"
    ]
)

bandar = {}

for item in bandar_objects:

    indicators = item.get(
        "indicators",
        {}
    )

    if (
        isinstance(indicators, dict)
        and
        (
            "top_broker_net_flow" in indicators
            or
            "large_lot_percentage" in indicators
            or
            "accumulation_score" in indicators
            or
            "foreign_flow" in indicators
        )
    ):

        bandar = item
        break


retail_indicators = retail.get(
    "indicators",
    {}
)

bandar_indicators = bandar.get(
    "indicators",
    {}
)


# ============================================================
# TOP BROKERS
# ============================================================

top_brokers = bandar.get(
    "top_brokers",
    {}
)

buyers = top_brokers.get(
    "buyers",
    []
)

sellers = top_brokers.get(
    "sellers",
    []
)


# ============================================================
# IPO MOMENTUM
# ============================================================

ipo_objects = find_objects(
    ipo_raw,
    ["status"]
)

ipo_sentiment = "-"

for item in ipo_objects:

    status = item.get(
        "status"
    )

    if status in [
        "BULLISH",
        "BEARISH",
        "NEUTRAL"
    ]:

        ipo_sentiment = status
        break


# fallback jika status berada di key sentiment
if ipo_sentiment == "-":

    sentiment_objects = find_objects(
        ipo_raw,
        ["sentiment"]
    )

    for item in sentiment_objects:

        value = item.get(
            "sentiment"
        )

        if isinstance(value, str):

            if value.upper() in [
                "BULLISH",
                "BEARISH",
                "NEUTRAL"
            ]:

                ipo_sentiment = value.upper()
                break


# ============================================================
# FINAL OBJECTS
# ============================================================

ipo_momentum = "-"

momentum_objects = find_objects(
    ipo_raw,
    ["momentum"]
)

if momentum_objects:

    value = momentum_objects[0].get(
        "momentum"
    )

    if isinstance(value, dict):

        ipo_momentum = (
            value.get("score")
            or value.get("value")
            or value.get("status")
            or "-"
        )

    else:
        ipo_momentum = value


ipo_score = "-"

score_objects = find_objects(
    ipo_raw,
    ["score"]
)

if score_objects:

    value = score_objects[0].get(
        "score"
    )

    if not isinstance(value, dict):
        ipo_score = value

Explanation

Cell 4 converts the raw API responses into structured objects that can be displayed by the final dashboard.

The first step decodes the raw data from all three API requests.


Insider Data Extraction

The insider response is searched for objects containing the required transaction fields.

The resulting records are converted into insider_rows.

Each row contains:


  • Symbol


  • Buy Shares


  • Sell Shares


  • Net Shares


  • Buy Value


  • Sell Value


  • Net Value


  • Buy Count


  • Sell Count


  • Insiders


  • Last Activity


  • Action

The list is then converted into a Pandas DataFrame called insider_df.

This makes the insider data easier to process and display.


Retail Sentiment Extraction

The code searches for sentiment objects containing:

  • score

  • status

  • indicators

  • danger_level

It then identifies the object associated with retail behaviour by checking for retail-specific indicators such as:

  • frequency_score

  • small_lot_percentage

  • fomo_score

This is an important design decision because the same API response may contain multiple sentiment objects.


Bandar / Smart Money Extraction

The same approach is used to identify the bandar or smart-money sentiment object.

The parser searches for indicators such as:

  • top_broker_net_flow

  • large_lot_percentage

  • accumulation_score

  • foreign_flow

Once identified, the code extracts the indicators object for later display.

The code also extracts the top broker buyers and sellers.

This allows the final dashboard to display the five largest buyer and seller brokers.


IPO Momentum Extraction

The IPO section searches the API response for a status field.

If it finds one of the recognised sentiment values:

  • BULLISH

  • BEARISH

  • NEUTRAL

the value becomes ipo_sentiment.

The code also includes a fallback mechanism.

If the sentiment is not found under status, it searches for a sentiment field.

Finally, the parser attempts to extract:


  • IPO momentum


  • IPO score

This makes the extraction process more tolerant of changes in the response structure.


Cell 5 — Final Dashboard

# ============================================================
# CELL 5 — FINAL DASHBOARD
# ============================================================

print("=" * 100)
print("IDX INSIDER, RETAIL BANDAR & IPO MOMENTUM DASHBOARD")
print("=" * 100)

print(
    f"🕒 Update : "
    f"{datetime.now().strftime('%d-%m-%Y %H:%M:%S')}"
)

print(
    f"📅 Insider : {PERIOD}   |   "
    f"📊 Sentiment : {SENTIMENT_SYMBOL} / {SENTIMENT_DAYS} hari"
)


# ============================================================
# API STATUS
# ============================================================

print("\n" + "=" * 100)
print("🔌 API STATUS")
print("=" * 100)

print(
    f"Insider Screening       : "
    f"HTTP {insider_result['status_code']} | "
    f"{get_status(insider_result)}"
)

print(
    f"Retail Bandar Sentiment : "
    f"HTTP {sentiment_result['status_code']} | "
    f"{get_status(sentiment_result)}"
)

print(
    f"IPO Momentum            : "
    f"HTTP {ipo_result['status_code']} | "
    f"{get_status(ipo_result)}"
)


# ============================================================
# INSIDER
# ============================================================

print("\n" + "=" * 100)
print("👤 INSIDER SCREENING")
print("=" * 100)

print(f"Periode      : {PERIOD}")
print(f"Symbol       : {SYMBOLS}")
print(f"Jumlah Data  : {len(insider_df)}")

if not insider_df.empty:

    for _, row in insider_df.iterrows():

        print("\n" + "-" * 100)

        print(
            f"🏷️  {row['Symbol']}   |   "
            f"Aksi : {row['Action']}"
        )

        print(
            f"📈 BUY  : "
            f"{format_number(row['Buy Shares'])} saham "
            f"| {format_billion(row['Buy Value'])}"
        )

        print(
            f"📉 SELL : "
            f"{format_number(row['Sell Shares'])} saham "
            f"| {format_billion(row['Sell Value'])}"
        )

        print(
            f"⚖️  NET  : "
            f"{format_number(row['Net Shares'])} saham "
            f"| {format_billion(row['Net Value'])}"
        )

        print(
            f"🔢 Transaksi : "
            f"Buy {row['Buy Count']}x | "
            f"Sell {row['Sell Count']}x"
        )

        print(
            f"📅 Aktivitas : "
            f"{row['Last Activity']}"
        )

        if row["Insiders"]:

            print("👥 Insider:")

            for person in row["Insiders"]:
                print(f"   • {person}")

else:

    print("Tidak ada data insider.")


# ============================================================
# RETAIL
# ============================================================

print("\n" + "=" * 100)
print("🛒 RETAIL SENTIMENT")
print("=" * 100)

print(f"Symbol        : {SENTIMENT_SYMBOL}")
print(f"Score         : {retail.get('score', '-')}")
print(f"Status        : {retail.get('status', '-')}")
print(f"Danger Level  : {retail.get('danger_level', '-')}")


print("\n📊 Retail Indicators")
print("-" * 100)

print(
    f"Frequency Score      : "
    f"{retail_indicators.get('frequency_score', '-')}"
)

print(
    f"Small Lot Percentage  : "
    f"{format_percent(retail_indicators.get('small_lot_percentage'))}"
)

print(
    f"FOMO Score           : "
    f"{retail_indicators.get('fomo_score', '-')}"
)

print(
    f"Volume Participation : "
    f"{format_percent(retail_indicators.get('volume_participation'))}"
)


# ============================================================
# BANDAR
# ============================================================

print("\n" + "=" * 100)
print("🐋 BANDAR / SMART MONEY SENTIMENT")
print("=" * 100)

print(
    f"Score         : "
    f"{bandar.get('score', '-')}"
)

print(
    f"Status        : "
    f"{bandar.get('status', '-')}"
)

print(
    f"Large Lot     : "
    f"{format_percent(bandar_indicators.get('large_lot_percentage'))}"
)

print(
    f"Accumulation  : "
    f"{bandar_indicators.get('accumulation_score', '-')}"
)

print(
    f"Foreign Flow  : "
    f"{format_billion(bandar_indicators.get('foreign_flow'))}"
)

print(
    f"Institutional : "
    f"{format_billion(bandar_indicators.get('institutional_flow'))}"
)


# ============================================================
# BROKER BUYERS
# ============================================================

print("\n📈 TOP BROKER BUYERS")
print("-" * 100)

if buyers:

    for i, broker in enumerate(
        buyers[:5],
        1
    ):

        print(
            f"{i:02d}. "
            f"{broker.get('code', '-'):4} | "
            f"{broker.get('type', '-'):12} | "
            f"Net Buy : "
            f"{broker.get('net_value_formatted', '-')}"
        )

else:

    print("Tidak ada data broker buyers.")


# ============================================================
# BROKER SELLERS
# ============================================================

print("\n📉 TOP BROKER SELLERS")
print("-" * 100)

if sellers:

    for i, broker in enumerate(
        sellers[:5],
        1
    ):

        print(
            f"{i:02d}. "
            f"{broker.get('code', '-'):4} | "
            f"{broker.get('type', '-'):12} | "
            f"Net Sell : "
            f"{broker.get('net_value_formatted', '-')}"
        )

else:

    print("Tidak ada data broker sellers.")


# ============================================================
# IPO MOMENTUM
# ============================================================

print("\n" + "=" * 100)
print("🚀 IPO MOMENTUM")
print("=" * 100)

print(
    f"Momentum  : {ipo_momentum}"
)

print(
    f"Sentiment : {ipo_sentiment}"
)

if ipo_score != "-":

    print(
        f"Score     : {ipo_score}"
    )


# ============================================================
# QUICK INSIGHT
# ============================================================

print("\n" + "=" * 100)
print("🧾 QUICK INSIGHT")
print("=" * 100)


if not insider_df.empty:

    action = str(
        insider_df.iloc[0]["Action"]
    ).upper()

    if action == "DISTRIBUTION":

        print(
            "👤 Insider : DISTRIBUTION — "
            "aktivitas insider didominasi penjualan."
        )

    elif action == "ACCUMULATION":

        print(
            "👤 Insider : ACCUMULATION — "
            "aktivitas insider didominasi pembelian."
        )

    else:

        print(
            f"👤 Insider : {action}"
        )


print(
    f"🛒 Retail  : "
    f"{retail.get('status', '-')}"
    f" | Score {retail.get('score', '-')}"
)

print(
    f"🐋 Bandar  : "
    f"{bandar.get('status', '-')}"
    f" | Score {bandar.get('score', '-')}"
)

print(
    f"🚀 IPO     : "
    f"{ipo_sentiment}"
)

print("\n" + "=" * 100)
print("✅ ANALISIS SELESAI")
print("=" * 100)

Explanation

Cell 5 is responsible for converting all previously processed data into the final market intelligence dashboard.

The dashboard begins by displaying:

  • Current update time.

  • Insider analysis period.

  • Sentiment symbol.

  • Sentiment period.

It then displays the status of all three API requests.


Insider Screening Dashboard

The insider section displays the selected period, symbols, and number of records.

For every insider record, the dashboard shows:

  • Stock symbol.

  • Dominant action.

  • Buy shares and value.

  • Sell shares and value.

  • Net shares and value.

  • Number of buy transactions.

  • Number of sell transactions.

  • Last activity.

  • Insider names.

This creates a compact transaction-oriented view of insider activity.


Retail Sentiment Dashboard

The retail section displays the sentiment score, status, and danger level.

It then provides the underlying retail indicators:

  • Frequency Score

  • Small Lot Percentage

  • FOMO Score

  • Volume Participation

These metrics are useful for understanding retail participation and behaviour around the selected stock.


Bandar / Smart Money Dashboard

The next section focuses on larger market participants.

The dashboard displays:

  • Smart-money score.

  • Status.

  • Large lot percentage.

  • Accumulation score.

  • Foreign flow.

  • Institutional flow.

This provides a complementary view to the retail sentiment section.


Top Broker Buyers and Sellers

The dashboard then displays the top five broker buyers and top five broker sellers.

For each broker, it shows:

  • Broker code.

  • Broker type.

  • Net buy or net sell value.

This provides a broker-flow perspective that can help complement the broader bandar sentiment analysis.


IPO Momentum

The IPO section displays the extracted:

  • Momentum.

  • Sentiment.

  • Score.

The sentiment is intended to represent the current IPO momentum classification returned by the API.


Quick Insight

The final section provides a simplified interpretation.

The code checks the first insider record's dominant action.

If the action is:

DISTRIBUTION

the dashboard reports that insider activity is dominated by selling.

If the action is:

ACCUMULATION

the dashboard reports that insider activity is dominated by buying.

It then displays the current retail status and score, bandar status and score, and IPO sentiment.


Results and Discussion

The completed dashboard combines several different market signals into one workflow.

1. Insider Activity

Insider screening provides information about buying and selling activity by company insiders.

The combination of buy shares, sell shares, and net shares allows the user to distinguish between accumulation and distribution patterns.

For example, a positive net share position may indicate that buying activity exceeds selling activity during the selected period.

However, insider activity should not automatically be interpreted as a bullish or bearish signal without considering the reason for the transaction and the context surrounding the company.


2. Retail Sentiment

The retail sentiment component adds another dimension to the analysis.

Metrics such as frequency score, small-lot percentage, and FOMO score attempt to quantify retail participation and behaviour.

A high level of retail activity does not necessarily mean that a stock will rise or fall. Instead, it can be treated as an additional market-behaviour indicator.


3. Bandar / Smart Money Sentiment

The bandar section focuses on indicators associated with larger market participants.

Large-lot activity, accumulation, foreign flow, institutional flow, and broker activity can provide additional information about the distribution of trading activity.

The value of this section comes from combining several indicators rather than relying on a single broker or transaction.


4. Broker Flow

Top broker buyers and sellers provide a more granular view of market participation.

The dashboard limits the display to the top five buyers and sellers, making the output easier to read.

This can be useful when attempting to identify which brokers are contributing significantly to net buying or selling activity.


5. IPO Momentum

The IPO momentum section extends the analysis beyond currently traded stocks.

By extracting sentiment, momentum, and score information, the dashboard can provide a high-level overview of IPO market conditions returned by the API.

This can potentially be used as an additional market-regime indicator.


Why This Dashboard Is Useful

One of the strengths of this project is that it does not rely on a single market indicator.

Instead, it combines:

Insider → Retail → Smart Money → Broker Flow → IPO

This creates a broader market-intelligence framework.

The resulting workflow can be useful for research, screening, monitoring, and further quantitative development.

The architecture is also relatively easy to extend. Additional API endpoints can be added to the same api_get() framework, while the recursive parser can be reused for new JSON structures.

Important Considerations

This dashboard should be treated as an analytical and research tool, not as an automatic trading system.

A classification such as ACCUMULATION, DISTRIBUTION, BULLISH, or BEARISH does not guarantee future price performance.

Market participants should also consider:

  • Company fundamentals.

  • Valuation.

  • Earnings growth.

  • Liquidity.

  • Corporate actions.

  • Macroeconomic conditions.

  • Market volatility.

  • Foreign market movements.

  • Overall risk management.

The API data should therefore be interpreted as one component within a broader investment research process.


Conclusion

The IDX Insider, Retail-Bandar Sentiment & IPO Momentum Analysis Using Python project demonstrates how multiple market-intelligence datasets can be integrated into a single Python workflow.

The project combines three major API analysis areas:

  • Insider Screening

  • Retail-Bandar Sentiment

  • IPO Momentum

It then adds broker-flow analysis, data normalization, error handling, recursive JSON parsing, numerical formatting, and a terminal-style dashboard.

The five-cell architecture keeps the workflow organized:

  1. Cell 1 handles imports and configuration.

  2. Cell 2 performs the three API requests.

  3. Cell 3 provides robust JSON parsing and formatting utilities.

  4. Cell 4 extracts and structures the market intelligence data.

  5. Cell 5 presents the final dashboard and quick insights.

This architecture provides a strong foundation for developing a more advanced IDX Market Intelligence System with Python, including historical signal tracking, automated alerts, visualization, portfolio integration, and quantitative scoring models.